From 375b7844823f3c135aea722666a07a64608036fb Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Mon, 1 Oct 2018 15:51:11 -0400 Subject: [PATCH 01/13] name import to fix goimports linting --- actions/app.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actions/app.go b/actions/app.go index 6ee52c59..450f1a8c 100644 --- a/actions/app.go +++ b/actions/app.go @@ -1,9 +1,10 @@ package actions import ( - "github.com/oysterprotocol/brokernode/utils" "os" + oyster_utils "github.com/oysterprotocol/brokernode/utils" + raven "github.com/getsentry/raven-go" "github.com/gobuffalo/buffalo" "github.com/gobuffalo/buffalo/middleware" From 754b4e9de6b4cd35c0705e48a469f42832491670 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Mon, 1 Oct 2018 16:27:50 -0400 Subject: [PATCH 02/13] oyster_utils named import --- main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 19a12e29..5cefba0b 100644 --- a/main.go +++ b/main.go @@ -2,15 +2,16 @@ package main import ( "fmt" - "github.com/fatih/color" - "github.com/oysterprotocol/brokernode/actions" - "github.com/oysterprotocol/brokernode/utils" "log" "math/rand" "os" "runtime" "time" + "github.com/fatih/color" + "github.com/oysterprotocol/brokernode/actions" + oyster_utils "github.com/oysterprotocol/brokernode/utils" + "github.com/gobuffalo/pop" "github.com/gobuffalo/pop/logging" ) From acd01fe50204adfa71f7625a5f469f25ef1432a4 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Mon, 1 Oct 2018 17:52:17 -0400 Subject: [PATCH 03/13] add copies of controllers v2 folder --- actions/v2/status.go | 29 ++ actions/v2/status_test.go | 21 + actions/v2/transaction_brokernodes.go | 199 ++++++++ actions/v2/transaction_brokernodes_test.go | 4 + actions/v2/transaction_genesis_hashes.go | 215 ++++++++ actions/v2/transaction_genesis_hashes_test.go | 4 + actions/v2/treasures.go | 99 ++++ actions/v2/treasures_test.go | 158 ++++++ actions/v2/upload_sessions.go | 465 ++++++++++++++++++ actions/v2/upload_sessions_test.go | 331 +++++++++++++ actions/v2/webnodes.go | 56 +++ 11 files changed, 1581 insertions(+) create mode 100644 actions/v2/status.go create mode 100644 actions/v2/status_test.go create mode 100644 actions/v2/transaction_brokernodes.go create mode 100644 actions/v2/transaction_brokernodes_test.go create mode 100644 actions/v2/transaction_genesis_hashes.go create mode 100644 actions/v2/transaction_genesis_hashes_test.go create mode 100644 actions/v2/treasures.go create mode 100644 actions/v2/treasures_test.go create mode 100644 actions/v2/upload_sessions.go create mode 100644 actions/v2/upload_sessions_test.go create mode 100644 actions/v2/webnodes.go diff --git a/actions/v2/status.go b/actions/v2/status.go new file mode 100644 index 00000000..ff0ff04b --- /dev/null +++ b/actions/v2/status.go @@ -0,0 +1,29 @@ +package actions + +import ( + "github.com/gobuffalo/buffalo" + "os" +) + +/*StatusResource is a resource for the status endpoint*/ +type StatusResource struct { + buffalo.Resource +} + +// Response structs +type checkStatusRes struct { + Available bool `json:"available"` +} + +/*CheckStatus checks conditions to determine if the brokernode is available. +We can add more conditions to this method as needed*/ +func (status *StatusResource) CheckStatus(c buffalo.Context) error { + + available := os.Getenv("DEPLOY_IN_PROGRESS") != "true" + + res := checkStatusRes{ + Available: available, + } + + return c.Render(200, r.JSON(res)) +} diff --git a/actions/v2/status_test.go b/actions/v2/status_test.go new file mode 100644 index 00000000..71d33b11 --- /dev/null +++ b/actions/v2/status_test.go @@ -0,0 +1,21 @@ +package actions + +import ( + "encoding/json" + "io/ioutil" +) + +func (suite *ActionSuite) Test_CheckStatus() { + res := suite.JSON("/api/v2/status").Get() + + suite.Equal(200, res.Code) + + // Parse response + resParsed := checkStatusRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(true, resParsed.Available == true || resParsed.Available == false) +} diff --git a/actions/v2/transaction_brokernodes.go b/actions/v2/transaction_brokernodes.go new file mode 100644 index 00000000..1ec3de9d --- /dev/null +++ b/actions/v2/transaction_brokernodes.go @@ -0,0 +1,199 @@ +package actions + +import ( + "fmt" + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/pop" + "github.com/gobuffalo/uuid" + "github.com/iotaledger/giota" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/utils" + "os" + "strconv" + "strings" +) + +type TransactionBrokernodeResource struct { + buffalo.Resource +} + +// Request Response structs +type BrokernodeAddressPow struct { + Address string `json:"address"` + Message string `json:"message"` + BranchTx string `json:"branchTx"` + TrunkTx string `json:"trunkTx"` +} + +type transactionBrokernodeCreateReq struct { + CurrentList []string `json:"currentList"` +} + +type transactionBrokernodeCreateRes struct { + ID uuid.UUID `json:"id"` + Pow BrokernodeAddressPow `json:"pow"` +} + +type transactionBrokernodeUpdateReq struct { + Trytes string `json:"trytes"` +} + +type transactionBrokernodeUpdateRes struct { + Purchase string `json:"purchase"` +} + +// Creates a transaction. + +func (usr *TransactionBrokernodeResource) Create(c buffalo.Context) error { + + if os.Getenv("TANGLE_MAINTENANCE") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) + } + + if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) + } + + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionBrokernodeResourceCreate, start) + + req := transactionBrokernodeCreateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + brokernode := models.Brokernode{} + + dataMap, dataMapNotFoundErr := models.GetChunkForWebnodePoW() + + existingAddresses := oyster_utils.StringsJoin(req.CurrentList, oyster_utils.StringsJoinDelim) + brokernodeNotFoundErr := models.DB.Where("address NOT IN (?)", existingAddresses).First(&brokernode) + + // DB results error if First() does not return any error. + if dataMapNotFoundErr != nil { + return c.Render(403, r.JSON(map[string]string{"error": "Cannot give proof of work because: " + + dataMapNotFoundErr.Error()})) + } + + // DB results error if First() does not return any error. + if brokernodeNotFoundErr != nil { + return c.Render(403, r.JSON(map[string]string{"error": "No brokernode addresses to sell"})) + } + + tips, err := IotaWrapper.GetTransactionsToApprove() + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + } + + dataMapKey := oyster_utils.GetBadgerKey([]string{dataMap.GenesisHash, strconv.FormatInt(dataMap.Idx, 10)}) + + transaction := models.Transaction{ + Type: models.TransactionTypeBrokernode, + Status: models.TransactionStatusPending, + DataMapID: dataMapKey, + GenesisHash: dataMap.GenesisHash, + Idx: dataMap.Idx, + Purchase: brokernode.Address, + } + + err = models.DB.Transaction(func(tx *pop.Connection) error { + + vErr, err := tx.ValidateAndCreate(&transaction) + if err != nil || vErr.HasAny() { + return fmt.Errorf("Unable to Save Transaction: %v, %v", vErr, err) + } + return nil + }) + if err != nil { + oyster_utils.LogIfError(err, nil) + return c.Error(400, err) + } + + res := transactionBrokernodeCreateRes{ + ID: transaction.ID, + Pow: BrokernodeAddressPow{ + Address: dataMap.Address, + Message: dataMap.Message, + BranchTx: string(tips.BranchTransaction), + TrunkTx: string(tips.TrunkTransaction), + }, + } + + return c.Render(200, r.JSON(res)) +} + +func (usr *TransactionBrokernodeResource) Update(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionBrokernodeResourceUpdate, start) + + req := transactionBrokernodeUpdateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + // Get transaction + t := &models.Transaction{} + transactionError := models.DB.Find(t, c.Param("id")) + + trytes, err := giota.ToTrytes(req.Trytes) + if err != nil { + oyster_utils.LogIfError(err, nil) + return c.Render(400, r.JSON(map[string]string{"error": err.Error()})) + } + iotaTransaction, iotaError := giota.NewTransaction(trytes) + + if transactionError != nil || iotaError != nil { + return c.Render(400, r.JSON(map[string]string{"error": "No transaction found"})) + } + + chunkDataInProgress := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) + chunkDataComplete := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) + + chunkToUse := chunkDataInProgress + if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && + oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { + chunkToUse = chunkDataComplete + } else if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && !oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { + return c.Render(400, r.JSON(map[string]string{"error": "Could not find data for specified chunk"})) + } + + address, addError := giota.ToAddress(chunkToUse.Address) + validAddress := addError == nil && address == iotaTransaction.Address + if !validAddress { + return c.Render(400, r.JSON(map[string]string{"error": "Address is invalid"})) + } + + _, messageErr := giota.ToTrytes(chunkToUse.Message) + validMessage := messageErr == nil && strings.Contains(fmt.Sprint(iotaTransaction.SignatureMessageFragment), + chunkToUse.Message) + if !validMessage { + return c.Render(400, r.JSON(map[string]string{"error": "Message is invalid"})) + } + + host_ip := os.Getenv("HOST_IP") + provider := "http://" + host_ip + ":14265" + iotaAPI := giota.NewAPI(provider, nil) + + iotaTransactions := []giota.Transaction{*iotaTransaction} + broadcastErr := iotaAPI.BroadcastTransactions(iotaTransactions) + + if broadcastErr != nil { + return c.Render(400, r.JSON(map[string]string{"error": "Broadcast to Tangle failed"})) + } + + models.DB.Transaction(func(tx *pop.Connection) error { + t.Status = models.TransactionStatusComplete + tx.ValidateAndSave(t) + + return nil + }) + + res := transactionBrokernodeUpdateRes{Purchase: t.Purchase} + + return c.Render(202, r.JSON(res)) +} diff --git a/actions/v2/transaction_brokernodes_test.go b/actions/v2/transaction_brokernodes_test.go new file mode 100644 index 00000000..66a517e7 --- /dev/null +++ b/actions/v2/transaction_brokernodes_test.go @@ -0,0 +1,4 @@ +package actions + +func (suite *ActionSuite) Test_Create_Transaction_brokernodes() {} +func (suite *ActionSuite) Test_Update_Transaction_brokernodes() {} diff --git a/actions/v2/transaction_genesis_hashes.go b/actions/v2/transaction_genesis_hashes.go new file mode 100644 index 00000000..25a9093d --- /dev/null +++ b/actions/v2/transaction_genesis_hashes.go @@ -0,0 +1,215 @@ +package actions + +import ( + "fmt" + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/pop" + "github.com/gobuffalo/uuid" + "github.com/iotaledger/giota" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/utils" + "math" + "os" + "strconv" + "strings" +) + +type TransactionGenesisHashResource struct { + buffalo.Resource +} + +// Request Response structs +type GenesisHashPow struct { + Address string `json:"address"` + Message string `json:"message"` + BranchTx string `json:"branchTx"` + TrunkTx string `json:"trunkTx"` +} + +type transactionGenesisHashCreateReq struct { + CurrentList []string `json:"currentList"` +} + +type transactionGenesisHashCreateRes struct { + ID uuid.UUID `json:"id"` + Pow GenesisHashPow `json:"pow"` +} + +type transactionGenesisHashUpdateReq struct { + Trytes string `json:"trytes"` +} + +type transactionGenesisHashUpdateRes struct { + Purchase string `json:"purchase"` + NumberOfChunks int `json:"numberOfChunks"` +} + +// Creates a transaction. +func (usr *TransactionGenesisHashResource) Create(c buffalo.Context) error { + + if os.Getenv("TANGLE_MAINTENANCE") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) + } + + if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) + } + + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionGenesisHashResourceCreate, start) + + req := transactionGenesisHashCreateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + storedGenesisHash, genesisHashNotFound := models.GetGenesisHashForWebnode(req.CurrentList) + + if genesisHashNotFound != nil { + return c.Render(403, r.JSON(map[string]string{"error": "No genesis hash available"})) + } + + dataMap, dataMapNotFoundErr := models.GetChunkForWebnodePoW() + + if dataMapNotFoundErr != nil { + return c.Render(403, r.JSON(map[string]string{"error": "Cannot give proof of work because: " + + dataMapNotFoundErr.Error()})) + } + + tips, err := IotaWrapper.GetTransactionsToApprove() + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + } + + dataMapKey := oyster_utils.GetBadgerKey([]string{dataMap.GenesisHash, strconv.FormatInt(dataMap.Idx, 10)}) + + t := models.Transaction{} + models.DB.Transaction(func(tx *pop.Connection) error { + + storedGenesisHash.WebnodeCount++ + if storedGenesisHash.WebnodeCount >= models.WebnodeCountLimit { + storedGenesisHash.Status = models.StoredGenesisHashAssigned + } + vErr, err := tx.ValidateAndSave(&storedGenesisHash) + oyster_utils.LogIfError(err, nil) + oyster_utils.LogIfValidationError("validation errors in transaction_genesis_hashes.", vErr, nil) + + t = models.Transaction{ + Type: models.TransactionTypeGenesisHash, + Status: models.TransactionStatusPending, + DataMapID: dataMapKey, + GenesisHash: dataMap.GenesisHash, + Idx: dataMap.Idx, + Purchase: storedGenesisHash.GenesisHash, + } + tx.ValidateAndSave(&t) + return nil + }) + + res := transactionGenesisHashCreateRes{ + ID: t.ID, + Pow: GenesisHashPow{ + Address: dataMap.Address, + Message: dataMap.Message, + BranchTx: string(tips.BranchTransaction), + TrunkTx: string(tips.TrunkTransaction), + }, + } + + return c.Render(200, r.JSON(res)) +} + +func (usr *TransactionGenesisHashResource) Update(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionGenesisHashResourceUpdate, start) + + req := transactionGenesisHashUpdateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + // Get transaction + t := &models.Transaction{} + transactionError := models.DB.Find(t, c.Param("id")) + if transactionError != nil { + return c.Render(400, r.JSON(map[string]string{"error": "No transaction found"})) + } + + trytes, err := giota.ToTrytes(req.Trytes) + if err != nil { + oyster_utils.LogIfError(err, nil) + return c.Render(400, r.JSON(map[string]string{"error": err.Error()})) + } + iotaTransaction, iotaError := giota.NewTransaction(trytes) + + if iotaError != nil { + return c.Render(400, r.JSON(map[string]string{"error": "Could not generate transaction object from trytes"})) + } + + chunkDataInProgress := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) + chunkDataComplete := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) + + chunkToUse := chunkDataInProgress + if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && + oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { + chunkToUse = chunkDataComplete + + } else if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && !oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { + return c.Render(400, r.JSON(map[string]string{"error": "Could not find data for specified chunk"})) + } + + address, addError := giota.ToAddress(chunkToUse.Address) + + validAddress := addError == nil && address == iotaTransaction.Address + if !validAddress { + return c.Render(400, r.JSON(map[string]string{"error": "Address is invalid"})) + } + + _, messageErr := giota.ToTrytes(chunkToUse.Message) + validMessage := messageErr == nil && strings.Contains(fmt.Sprint(iotaTransaction.SignatureMessageFragment), + chunkToUse.Message) + if !validMessage { + return c.Render(400, r.JSON(map[string]string{"error": "Message is invalid"})) + } + + host_ip := os.Getenv("HOST_IP") + provider := "http://" + host_ip + ":14265" + iotaAPI := giota.NewAPI(provider, nil) + + iotaTransactions := []giota.Transaction{*iotaTransaction} + broadcastErr := iotaAPI.BroadcastTransactions(iotaTransactions) + + if broadcastErr != nil { + return c.Render(400, r.JSON(map[string]string{"error": "Broadcast to Tangle failed"})) + } + + storedGenesisHash := models.StoredGenesisHash{} + genesisHashNotFound := models.DB.Limit(1).Where("genesis_hash = ?", t.Purchase).First(&storedGenesisHash) + + if genesisHashNotFound != nil { + return c.Render(403, r.JSON(map[string]string{"error": "Stored genesis hash was not found"})) + } + + models.DB.Transaction(func(tx *pop.Connection) error { + t.Status = models.TransactionStatusComplete + tx.ValidateAndSave(t) + + storedGenesisHash.Status = models.StoredGenesisHashUnassigned + storedGenesisHash.WebnodeCount = storedGenesisHash.WebnodeCount + 1 + tx.ValidateAndSave(&storedGenesisHash) + + return nil + }) + + res := transactionGenesisHashUpdateRes{ + Purchase: t.Purchase, + NumberOfChunks: int(math.Ceil(float64(storedGenesisHash.FileSizeBytes) / models.FileBytesChunkSize)), + } + + return c.Render(202, r.JSON(res)) +} diff --git a/actions/v2/transaction_genesis_hashes_test.go b/actions/v2/transaction_genesis_hashes_test.go new file mode 100644 index 00000000..2c01c2ee --- /dev/null +++ b/actions/v2/transaction_genesis_hashes_test.go @@ -0,0 +1,4 @@ +package actions + +func (suite *ActionSuite) Test_Create_Transaction_genesis_hashes() {} +func (suite *ActionSuite) Test_Update_Transaction_genesis_hashes() {} diff --git a/actions/v2/treasures.go b/actions/v2/treasures.go new file mode 100644 index 00000000..71567486 --- /dev/null +++ b/actions/v2/treasures.go @@ -0,0 +1,99 @@ +package actions + +import ( + "errors" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/gobuffalo/buffalo" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/utils" +) + +type TreasuresResource struct { + buffalo.Resource +} + +type treasureReq struct { + ReceiverEthAddr string `json:"receiverEthAddr"` + GenesisHash string `json:"genesisHash"` + SectorIdx int `json:"sectorIdx"` + NumChunks int `json:"numChunks"` + EthKey string `json:"ethKey"` +} + +type treasureRes struct { + Success bool `json:"success"` +} + +// Verifies the treasure and claims such treasure. +func (t *TreasuresResource) VerifyAndClaim(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTreasuresResourceVerifyAndClaim, start) + + req := treasureReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + if req.EthKey == os.Getenv("TEST_MODE_WALLET_KEY") { + return c.Render(200, r.JSON(treasureRes{ + Success: true, + })) + } + + addr := oyster_utils.ComputeSectorDataMapAddress(req.GenesisHash, req.SectorIdx, req.NumChunks) + verify, err := IotaWrapper.VerifyTreasure(addr) + + _, keyErr := crypto.HexToECDSA(req.EthKey) + + if err != nil { + c.Error(400, err) + } + if keyErr != nil { + c.Error(400, keyErr) + } + if !verify { + return c.Render(200, r.JSON(treasureRes{ + Success: verify, + })) + } + + ethAddr := EthWrapper.GenerateEthAddrFromPrivateKey(req.EthKey) + + startingClaimClock, claimClockErr := EthWrapper.CheckClaimClock(ethAddr) + + if startingClaimClock.Int64() == int64(0) { + c.Error(400, errors.New("claim clock should be 1 or a timestamp but received 0")) + } else if claimClockErr != nil { + c.Error(400, claimClockErr) + } else { + webnodeTreasureClaim := models.WebnodeTreasureClaim{ + GenesisHash: req.GenesisHash, + SectorIdx: req.SectorIdx, + NumChunks: req.NumChunks, + ReceiverETHAddr: req.ReceiverEthAddr, + TreasureETHAddr: ethAddr.String(), + TreasureETHPrivateKey: req.EthKey, + StartingClaimClock: startingClaimClock.Int64(), + } + + vErr, err := models.DB.ValidateAndCreate(&webnodeTreasureClaim) + + oyster_utils.LogIfError(errors.New(vErr.Error()), nil) + oyster_utils.LogIfError(err, nil) + + verify = err == nil && len(vErr.Errors) == 0 + } + + res := treasureRes{ + Success: verify && + startingClaimClock.Int64() != int64(0) && + claimClockErr == nil, + } + + return c.Render(200, r.JSON(res)) +} diff --git a/actions/v2/treasures_test.go b/actions/v2/treasures_test.go new file mode 100644 index 00000000..cd062428 --- /dev/null +++ b/actions/v2/treasures_test.go @@ -0,0 +1,158 @@ +package actions + +import ( + "encoding/json" + "errors" + "github.com/ethereum/go-ethereum/common" + "github.com/oysterprotocol/brokernode/services" + "io/ioutil" + "math/big" +) + +// Record data for VerifyTreasure method +type mockVerifyTreasure struct { + hasCalled bool + input_addr []string + output_bool bool + output_error error +} + +var checkClaimClockCalled = false +var ethAddressCalledWithCheckClaimClock common.Address + +func (suite *ActionSuite) Test_VerifyTreasureAndClaim_Success() { + checkClaimClockCalled = false + + mockVerifyTreasure := mockVerifyTreasure{ + output_bool: true, + output_error: nil, + } + IotaWrapper = services.IotaService{ + VerifyTreasure: mockVerifyTreasure.verifyTreasure, + } + EthWrapper = services.Eth{ + GenerateEthAddrFromPrivateKey: EthWrapper.GenerateEthAddrFromPrivateKey, + CheckClaimClock: func(address common.Address) (*big.Int, error) { + checkClaimClockCalled = true + ethAddressCalledWithCheckClaimClock = address + return big.NewInt(1), nil + }, + } + + ethKey := "9999999999999999999999999999999999999999999999999999999999999991" + addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) + + res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ + "receiverEthAddr": addr, + "genesisHash": "1234", + "sectorIdx": 1, + "numChunks": 5, + "ethKey": ethKey, + }) + + suite.Equal(200, res.Code) + + // Check mockVerifyTreasure + suite.True(mockVerifyTreasure.hasCalled) + suite.Equal(5, len(mockVerifyTreasure.input_addr)) + + suite.Equal(addr, ethAddressCalledWithCheckClaimClock) + suite.Equal(true, checkClaimClockCalled) + + // Parse response + resParsed := treasureRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(true, resParsed.Success) +} + +func (suite *ActionSuite) Test_VerifyTreasure_FailureWithError() { + + checkClaimClockCalled = false + + m := mockVerifyTreasure{ + output_bool: false, + output_error: errors.New("Invalid address"), + } + IotaWrapper = services.IotaService{ + VerifyTreasure: m.verifyTreasure, + } + + ethKey := "9999999999999999999999999999999999999999999999999999999999999991" + addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) + + res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ + "receiverEthAddr": addr, + "genesisHash": "1234", + "sectorIdx": 1, + "numChunks": 5, + }) + + suite.True(m.hasCalled) + suite.Equal(5, len(m.input_addr)) + + // Parse response + resParsed := treasureRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(false, resParsed.Success) +} + +func (suite *ActionSuite) Test_Check_Claim_Clock_Error() { + + checkClaimClockCalled = false + + mockVerifyTreasure := mockVerifyTreasure{ + output_bool: true, + output_error: nil, + } + IotaWrapper = services.IotaService{ + VerifyTreasure: mockVerifyTreasure.verifyTreasure, + } + EthWrapper = services.Eth{ + GenerateEthAddrFromPrivateKey: EthWrapper.GenerateEthAddrFromPrivateKey, + CheckClaimClock: func(address common.Address) (*big.Int, error) { + ethAddressCalledWithCheckClaimClock = address + checkClaimClockCalled = true + return big.NewInt(-1), errors.New("error") + }, + } + + ethKey := "9999999999999999999999999999999999999999999999999999999999999991" + addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) + + res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ + "receiverEthAddr": addr, + "genesisHash": "1234", + "sectorIdx": 1, + "numChunks": 5, + "ethKey": ethKey, + }) + + suite.Equal(200, res.Code) + + suite.True(mockVerifyTreasure.hasCalled) + suite.True(checkClaimClockCalled) + + // Parse response + resParsed := treasureRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(false, resParsed.Success) +} + +// For mocking VerifyTreasure method +func (v *mockVerifyTreasure) verifyTreasure(addr []string) (bool, error) { + v.hasCalled = true + v.input_addr = addr + return v.output_bool, v.output_error +} diff --git a/actions/v2/upload_sessions.go b/actions/v2/upload_sessions.go new file mode 100644 index 00000000..af121296 --- /dev/null +++ b/actions/v2/upload_sessions.go @@ -0,0 +1,465 @@ +package actions + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "time" + + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/pop/nulls" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/services" + "github.com/oysterprotocol/brokernode/utils" + "github.com/pkg/errors" + "gopkg.in/segmentio/analytics-go.v3" +) + +type UploadSessionResource struct { + buffalo.Resource +} + +// Request Response structs + +type uploadSessionCreateReq struct { + GenesisHash string `json:"genesisHash"` + NumChunks int `json:"numChunks"` + FileSizeBytes uint64 `json:"fileSizeBytes"` // This is Trytes instead of Byte + BetaIP string `json:"betaIp"` + StorageLengthInYears int `json:"storageLengthInYears"` + AlphaTreasureIndexes []int `json:"alphaTreasureIndexes"` + Invoice models.Invoice `json:"invoice"` + Version uint32 `json:"version"` +} + +type uploadSessionCreateRes struct { + ID string `json:"id"` + UploadSession models.UploadSession `json:"uploadSession"` + BetaSessionID string `json:"betaSessionId"` + Invoice models.Invoice `json:"invoice"` +} + +type uploadSessionCreateBetaRes struct { + ID string `json:"id"` + UploadSession models.UploadSession `json:"uploadSession"` + BetaSessionID string `json:"betaSessionId"` + Invoice models.Invoice `json:"invoice"` + BetaTreasureIndexes []int `json:"betaTreasureIndexes"` +} + +type UploadSessionUpdateReq struct { + Chunks []models.ChunkReq `json:"chunks"` +} + +type paymentStatusCreateRes struct { + ID string `json:"id"` + PaymentStatus string `json:"paymentStatus"` +} + +var NumChunksLimit = -1 //unlimited + +func init() { + +} + +// Create creates an upload session. +func (usr *UploadSessionResource) Create(c buffalo.Context) error { + + if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { + err := errors.New("Deployment in progress. Try again later") + fmt.Println(err) + c.Error(400, err) + return err + } + + if v, err := strconv.Atoi(os.Getenv("NUM_CHUNKS_LIMIT")); err == nil { + NumChunksLimit = v + } + + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreate, start) + + req := uploadSessionCreateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + if NumChunksLimit != -1 && req.NumChunks > NumChunksLimit { + err := errors.New("This broker has a limit of " + fmt.Sprint(NumChunksLimit) + " file chunks.") + fmt.Println(err) + c.Error(400, err) + return err + } + + alphaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() + + // Start Alpha Session. + alphaSession := models.UploadSession{ + Type: models.SessionTypeAlpha, + GenesisHash: req.GenesisHash, + FileSizeBytes: req.FileSizeBytes, + NumChunks: req.NumChunks, + StorageLengthInYears: req.StorageLengthInYears, + ETHAddrAlpha: nulls.NewString(alphaEthAddr.Hex()), + ETHPrivateKey: privKey, + Version: req.Version, + } + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_alpha_session", analytics.NewProperties(). + Set("id", alphaSession.ID). + Set("genesis_hash", alphaSession.GenesisHash). + Set("file_size_byes", alphaSession.FileSizeBytes). + Set("num_chunks", alphaSession.NumChunks). + Set("storage_years", alphaSession.StorageLengthInYears)) + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + + dbID := []string{oyster_utils.InProgressDir, alphaSession.GenesisHash, oyster_utils.HashDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for hashes") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + vErr, err := alphaSession.StartUploadSession() + if err != nil || vErr.HasAny() { + err = fmt.Errorf("StartUploadSession error: %v and validation error: %v", err, vErr) + c.Error(400, err) + return err + } + + invoice := alphaSession.GetInvoice() + + // Mutates this because copying in golang sucks... + req.Invoice = invoice + + req.AlphaTreasureIndexes = oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) + + // Start Beta Session. + var betaSessionID = "" + var betaTreasureIndexes []int + hasBeta := req.BetaIP != "" + if hasBeta { + betaReq, err := json.Marshal(req) + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + reqBetaBody := bytes.NewBuffer(betaReq) + + // Should we be hardcoding the port? + betaURL := req.BetaIP + ":3000/api/v2/upload-sessions/beta" + betaRes, err := http.Post(betaURL, "application/json", reqBetaBody) + defer betaRes.Body.Close() // we need to close the connection + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + betaSessionRes := &uploadSessionCreateBetaRes{} + if err := oyster_utils.ParseResBody(betaRes, betaSessionRes); err != nil { + err = fmt.Errorf("Unable to communicate with Beta node: %v", err) + // This should consider as BadRequest since the client pick the beta node. + c.Error(400, err) + return err + } + + betaSessionID = betaSessionRes.ID + + betaTreasureIndexes = betaSessionRes.BetaTreasureIndexes + alphaSession.ETHAddrBeta = betaSessionRes.UploadSession.ETHAddrBeta + } + + if err := models.DB.Save(&alphaSession); err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + models.NewBrokerBrokerTransaction(&alphaSession) + + if hasBeta { + mergedIndexes, _ := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, + oyster_utils.FileSectorInChunkSize, req.NumChunks) + + if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { + err := errors.New("no indexes selected for treasure") + fmt.Println(err) + c.Error(400, err) + return err + } + + for { + privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) + if err != nil { + err := errors.New("Could not generate eth keys: " + err.Error()) + fmt.Println(err) + c.Error(400, err) + return err + } + if len(mergedIndexes) != len(privateKeys) { + err := errors.New("privateKeys and mergedIndexes should have the same length") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + // Update alpha treasure idx map. + alphaSession.MakeTreasureIdxMap(mergedIndexes, privateKeys) + + treasureIndexes, _ := alphaSession.GetTreasureIndexes() + + if alphaSession.TreasureStatus == models.TreasureInDataMapPending && + alphaSession.TreasureIdxMap.Valid && alphaSession.TreasureIdxMap.String != "" && + len(treasureIndexes) == len(mergedIndexes) { + models.DB.ValidateAndUpdate(&alphaSession) + break + } + } + } + + res := uploadSessionCreateRes{ + UploadSession: alphaSession, + ID: alphaSession.ID.String(), + BetaSessionID: betaSessionID, + Invoice: invoice, + } + //go waitForTransferAndNotifyBeta( + // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) + + return c.Render(200, r.JSON(res)) +} + +// Update uploads a chunk associated with an upload session. +func (usr *UploadSessionResource) Update(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceUpdate, start) + + req := UploadSessionUpdateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + // Get session + uploadSession := &models.UploadSession{} + err := models.DB.Find(uploadSession, c.Param("id")) + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: updating_session", analytics.NewProperties(). + Set("id", uploadSession.ID). + Set("genesis_hash", uploadSession.GenesisHash). + Set("file_size_byes", uploadSession.FileSizeBytes). + Set("num_chunks", uploadSession.NumChunks). + Set("storage_years", uploadSession.StorageLengthInYears)) + + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + if uploadSession == nil { + err := errors.New("Error finding sessions") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + treasureIdxMap, err := uploadSession.GetTreasureIndexes() + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + dbID := []string{oyster_utils.InProgressDir, uploadSession.GenesisHash, oyster_utils.MessageDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for messages") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + // Update dMaps to have chunks async + go func() { + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: async_datamap_updates", analytics.NewProperties(). + Set("id", uploadSession.ID). + Set("genesis_hash", uploadSession.GenesisHash). + Set("file_size_byes", uploadSession.FileSizeBytes). + Set("num_chunks", uploadSession.NumChunks). + Set("storage_years", uploadSession.StorageLengthInYears)) + + models.ProcessAndStoreChunkData(req.Chunks, uploadSession.GenesisHash, treasureIdxMap, + models.DataMapsTimeToLive) + }() + + return c.Render(202, r.JSON(map[string]bool{"success": true})) +} + +// CreateBeta creates an upload session on the beta broker. +func (usr *UploadSessionResource) CreateBeta(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreateBeta, start) + + req := uploadSessionCreateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + betaTreasureIndexes := oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) + + // Generates ETH address. + betaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() + + u := models.UploadSession{ + Type: models.SessionTypeBeta, + GenesisHash: req.GenesisHash, + NumChunks: req.NumChunks, + FileSizeBytes: req.FileSizeBytes, + StorageLengthInYears: req.StorageLengthInYears, + TotalCost: req.Invoice.Cost, + ETHAddrAlpha: req.Invoice.EthAddress, + ETHAddrBeta: nulls.NewString(betaEthAddr.Hex()), + ETHPrivateKey: privKey, + Version: req.Version, + } + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_beta_session", analytics.NewProperties(). + Set("id", u.ID). + Set("genesis_hash", u.GenesisHash). + Set("file_size_byes", u.FileSizeBytes). + Set("num_chunks", u.NumChunks). + Set("storage_years", u.StorageLengthInYears)) + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + dbID := []string{oyster_utils.InProgressDir, u.GenesisHash, oyster_utils.HashDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for hashes") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + vErr, err := u.StartUploadSession() + + if err != nil || vErr.HasAny() { + err = fmt.Errorf("Can't startUploadSession with validation error: %v and err: %v", vErr, err) + c.Error(400, err) + return err + } + + if len(vErr.Errors) > 0 { + c.Render(422, r.JSON(vErr.Errors)) + return err + } + + mergedIndexes, err := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, + oyster_utils.FileSectorInChunkSize, req.NumChunks) + + if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { + err := errors.New("no indexes selected for treasure") + fmt.Println(err) + c.Error(400, err) + return err + } + + if err != nil { + fmt.Println(err) + c.Error(400, err) + return err + } + for { + privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) + if err != nil { + err := errors.New("Could not generate eth keys: " + err.Error()) + fmt.Println(err) + c.Error(400, err) + return err + } + if len(mergedIndexes) != len(privateKeys) { + err := errors.New("privateKeys and mergedIndexes should have the same length") + fmt.Println(err) + c.Error(400, err) + return err + } + u.MakeTreasureIdxMap(mergedIndexes, privateKeys) + + treasureIndexes, err := u.GetTreasureIndexes() + + if u.TreasureStatus == models.TreasureInDataMapPending && + u.TreasureIdxMap.Valid && u.TreasureIdxMap.String != "" && + len(treasureIndexes) == len(mergedIndexes) { + models.DB.ValidateAndUpdate(&u) + break + } + } + + models.NewBrokerBrokerTransaction(&u) + + res := uploadSessionCreateBetaRes{ + UploadSession: u, + ID: u.ID.String(), + Invoice: u.GetInvoice(), + BetaTreasureIndexes: betaTreasureIndexes, + } + //go waitForTransferAndNotifyBeta( + // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) + + return c.Render(200, r.JSON(res)) +} + +func (usr *UploadSessionResource) GetPaymentStatus(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceGetPaymentStatus, start) + + session := models.UploadSession{} + err := models.DB.Find(&session, c.Param("id")) + + if err != nil { + c.Error(400, err) + oyster_utils.LogIfError(err, nil) + return err + } + if (session == models.UploadSession{}) { + err := errors.New("Did not find session that matched id" + c.Param("id")) + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + // Force to check the status + if session.PaymentStatus != models.PaymentStatusConfirmed { + balance := EthWrapper.CheckPRLBalance(services.StringToAddress(session.ETHAddrAlpha.String)) + if balance.Int64() > 0 { + previousPaymentStatus := session.PaymentStatus + session.PaymentStatus = models.PaymentStatusConfirmed + err = models.DB.Save(&session) + if err != nil { + session.PaymentStatus = previousPaymentStatus + } else { + models.SetBrokerTransactionToPaid(session) + } + } + } + + res := paymentStatusCreateRes{ + ID: session.ID.String(), + PaymentStatus: session.GetPaymentStatus(), + } + + return c.Render(200, r.JSON(res)) +} diff --git a/actions/v2/upload_sessions_test.go b/actions/v2/upload_sessions_test.go new file mode 100644 index 00000000..98f58f50 --- /dev/null +++ b/actions/v2/upload_sessions_test.go @@ -0,0 +1,331 @@ +package actions + +import ( + "encoding/json" + "fmt" + "github.com/oysterprotocol/brokernode/utils" + "io/ioutil" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/gobuffalo/pop/nulls" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/services" +) + +type mockWaitForTransfer struct { + hasCalled bool + input_brokerAddr common.Address + output_int *big.Int + output_error error +} + +type mockSendPrl struct { + hasCalled bool + input_msg services.OysterCallMsg + output_bool bool +} + +type mockCheckPRLBalance struct { + hasCalled bool + input_addr common.Address + output_int *big.Int +} + +func (suite *ActionSuite) Test_UploadSessionsCreate() { + mockWaitForTransfer := mockWaitForTransfer{ + output_error: nil, + output_int: big.NewInt(100), + } + mockSendPrl := mockSendPrl{ + output_bool: true, + } + EthWrapper = services.Eth{ + WaitForTransfer: mockWaitForTransfer.waitForTransfer, + SendPRL: mockSendPrl.sendPrl, + GenerateEthAddr: services.EthWrapper.GenerateEthAddr, + GenerateKeys: services.EthWrapper.GenerateKeys, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + res := suite.JSON("/api/v2/upload-sessions").Post(map[string]interface{}{ + "genesisHash": genHash, + "fileSizeBytes": 123, + "numChunks": 2, + "storageLengthInYears": 1, + }) + + // Parse response + resParsed := uploadSessionCreateRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(200, res.Code) + suite.Equal(genHash, resParsed.UploadSession.GenesisHash) + suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) + suite.Equal(models.SessionTypeAlpha, resParsed.UploadSession.Type) + suite.NotEqual(0, resParsed.Invoice.Cost) + suite.NotEqual("", resParsed.Invoice.EthAddress) + + time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + //suite.True(mockWaitForTransfer.hasCalled) + //suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) + + // mockCheckPRLBalance will result a positive value, and Alpha knows that beta has such balance, it won't send + // it again. + suite.False(mockSendPrl.hasCalled) + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + // verifyPaymentConfirmation(as, resParsed.ID) + + chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) + + suite.Equal(genHash, chunkData.Hash) + + brokerTx := []models.BrokerBrokerTransaction{} + + suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) + + suite.Equal(1, len(brokerTx)) +} + +func (suite *ActionSuite) Test_UploadSessionsCreateBeta() { + mockWaitForTransfer := mockWaitForTransfer{ + output_error: nil, + output_int: big.NewInt(100), + } + mockSendPrl := mockSendPrl{} + + EthWrapper = services.Eth{ + WaitForTransfer: mockWaitForTransfer.waitForTransfer, + SendPRL: mockSendPrl.sendPrl, + GenerateEthAddr: services.EthWrapper.GenerateEthAddr, + GenerateKeys: services.EthWrapper.GenerateKeys, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + res := suite.JSON("/api/v2/upload-sessions/beta").Post(map[string]interface{}{ + "genesisHash": genHash, + "fileSizeBytes": 123, + "numChunks": 2, + "storageLengthInYears": 1, + "alphaTreasureIndexes": []int{1}, + }) + + // Parse response + resParsed := uploadSessionCreateBetaRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(200, res.Code) + suite.Equal(genHash, resParsed.UploadSession.GenesisHash) + suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) + suite.Equal(models.SessionTypeBeta, resParsed.UploadSession.Type) + suite.Equal(1, len(resParsed.BetaTreasureIndexes)) + suite.NotEqual(0, resParsed.Invoice.Cost) + suite.NotEqual("", resParsed.Invoice.EthAddress) + + time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out this test. + //suite.True(mockWaitForTransfer.hasCalled) + suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) + suite.False(mockSendPrl.hasCalled) + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + // verifyPaymentConfirmation(as, resParsed.ID) + + chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) + + suite.Equal(genHash, chunkData.Hash) + + brokerTx := []models.BrokerBrokerTransaction{} + + suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) + + suite.Equal(1, len(brokerTx)) +} + +func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_Paid() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusConfirmed, + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + suite.False(mockCheckPRLBalance.hasCalled) +} + +func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_NoConfirmButCheckComplete() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(10), + } + mockSendPrl := mockSendPrl{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + SendPRL: mockSendPrl.sendPrl, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusPending, + ETHAddrAlpha: nulls.NewString("alpha"), + ETHAddrBeta: nulls.NewString("beta"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + + /* checkPRLBalance has been called once just for alpha. Sending + to beta now occurs in a job. */ + suite.True(mockCheckPRLBalance.hasCalled) + suite.False(mockSendPrl.hasCalled) + suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_NoConfirmAndCheckIncomplete() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(0), + } + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusInvoiced, + ETHAddrAlpha: nulls.NewString("alpha"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("invoiced", resParsed.PaymentStatus) + suite.True(mockCheckPRLBalance.hasCalled) + suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusInvoiced, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_BetaConfirmed() { + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(10), + } + mockSendPrl := mockSendPrl{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + SendPRL: mockSendPrl.sendPrl, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + Type: models.SessionTypeBeta, + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusInvoiced, + ETHAddrAlpha: nulls.NewString("alpha"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + suite.True(mockCheckPRLBalance.hasCalled) + suite.False(mockSendPrl.hasCalled) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_DoesntExist() { + //res := suite.JSON("/api/v2/upload-sessions/" + "noIDFound").Get() + + //TODO: Return better error response when ID does not exist +} + +func getPaymentStatus(seededUploadSession models.UploadSession, suite *ActionSuite) paymentStatusCreateRes { + seededUploadSession.StartUploadSession() + + session := models.UploadSession{} + suite.Nil(suite.DB.Where("genesis_hash = ?", seededUploadSession.GenesisHash).First(&session)) + + //execute method + res := suite.JSON("/api/v2/upload-sessions/" + fmt.Sprint(session.ID)).Get() + + // Parse response + resParsed := paymentStatusCreateRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + + suite.Nil(json.Unmarshal(bodyBytes, &resParsed)) + + return resParsed +} + +func verifyPaymentConfirmation(sessionId string, suite *ActionSuite) { + session := models.UploadSession{} + err := suite.DB.Find(&session, sessionId) + suite.Nil(err) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} + +func (v *mockWaitForTransfer) waitForTransfer(brokerAddr common.Address, transferType string) (*big.Int, error) { + v.hasCalled = true + v.input_brokerAddr = brokerAddr + return v.output_int, v.output_error +} + +func (v *mockSendPrl) sendPrl(msg services.OysterCallMsg) bool { + v.hasCalled = true + v.input_msg = msg + return v.output_bool +} + +func (v *mockCheckPRLBalance) checkPRLBalance(addr common.Address) *big.Int { + v.hasCalled = true + v.input_addr = addr + return v.output_int +} diff --git a/actions/v2/webnodes.go b/actions/v2/webnodes.go new file mode 100644 index 00000000..3037f1a1 --- /dev/null +++ b/actions/v2/webnodes.go @@ -0,0 +1,56 @@ +package actions + +import ( + "fmt" + "os" + + "github.com/gobuffalo/buffalo" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/utils" +) + +type WebnodeResource struct { + buffalo.Resource +} + +// Request Response structs + +type webnodeCreateReq struct { + Address string `json:"address"` +} + +type webnodeCreateRes struct { + Webnode models.Webnode `json:"id"` +} + +// Creates a webnode. +func (usr *WebnodeResource) Create(c buffalo.Context) error { + + if os.Getenv("TANGLE_MAINTENANCE") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) + } + + if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { + return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) + } + + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramWebnodeResourceCreate, start) + + req := webnodeCreateReq{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + w := models.Webnode{ + Address: req.Address, + } + + res := webnodeCreateRes{ + Webnode: w, + } + + return c.Render(200, r.JSON(res)) +} From 26cb5b32b958b796a14b9c5708faae86dbe43ae4 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Wed, 3 Oct 2018 10:58:03 -0400 Subject: [PATCH 04/13] Revert "add copies of controllers v2 folder" This reverts commit acd01fe50204adfa71f7625a5f469f25ef1432a4. --- actions/v2/status.go | 29 -- actions/v2/status_test.go | 21 - actions/v2/transaction_brokernodes.go | 199 -------- actions/v2/transaction_brokernodes_test.go | 4 - actions/v2/transaction_genesis_hashes.go | 215 -------- actions/v2/transaction_genesis_hashes_test.go | 4 - actions/v2/treasures.go | 99 ---- actions/v2/treasures_test.go | 158 ------ actions/v2/upload_sessions.go | 465 ------------------ actions/v2/upload_sessions_test.go | 331 ------------- actions/v2/webnodes.go | 56 --- 11 files changed, 1581 deletions(-) delete mode 100644 actions/v2/status.go delete mode 100644 actions/v2/status_test.go delete mode 100644 actions/v2/transaction_brokernodes.go delete mode 100644 actions/v2/transaction_brokernodes_test.go delete mode 100644 actions/v2/transaction_genesis_hashes.go delete mode 100644 actions/v2/transaction_genesis_hashes_test.go delete mode 100644 actions/v2/treasures.go delete mode 100644 actions/v2/treasures_test.go delete mode 100644 actions/v2/upload_sessions.go delete mode 100644 actions/v2/upload_sessions_test.go delete mode 100644 actions/v2/webnodes.go diff --git a/actions/v2/status.go b/actions/v2/status.go deleted file mode 100644 index ff0ff04b..00000000 --- a/actions/v2/status.go +++ /dev/null @@ -1,29 +0,0 @@ -package actions - -import ( - "github.com/gobuffalo/buffalo" - "os" -) - -/*StatusResource is a resource for the status endpoint*/ -type StatusResource struct { - buffalo.Resource -} - -// Response structs -type checkStatusRes struct { - Available bool `json:"available"` -} - -/*CheckStatus checks conditions to determine if the brokernode is available. -We can add more conditions to this method as needed*/ -func (status *StatusResource) CheckStatus(c buffalo.Context) error { - - available := os.Getenv("DEPLOY_IN_PROGRESS") != "true" - - res := checkStatusRes{ - Available: available, - } - - return c.Render(200, r.JSON(res)) -} diff --git a/actions/v2/status_test.go b/actions/v2/status_test.go deleted file mode 100644 index 71d33b11..00000000 --- a/actions/v2/status_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package actions - -import ( - "encoding/json" - "io/ioutil" -) - -func (suite *ActionSuite) Test_CheckStatus() { - res := suite.JSON("/api/v2/status").Get() - - suite.Equal(200, res.Code) - - // Parse response - resParsed := checkStatusRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(true, resParsed.Available == true || resParsed.Available == false) -} diff --git a/actions/v2/transaction_brokernodes.go b/actions/v2/transaction_brokernodes.go deleted file mode 100644 index 1ec3de9d..00000000 --- a/actions/v2/transaction_brokernodes.go +++ /dev/null @@ -1,199 +0,0 @@ -package actions - -import ( - "fmt" - "github.com/gobuffalo/buffalo" - "github.com/gobuffalo/pop" - "github.com/gobuffalo/uuid" - "github.com/iotaledger/giota" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/utils" - "os" - "strconv" - "strings" -) - -type TransactionBrokernodeResource struct { - buffalo.Resource -} - -// Request Response structs -type BrokernodeAddressPow struct { - Address string `json:"address"` - Message string `json:"message"` - BranchTx string `json:"branchTx"` - TrunkTx string `json:"trunkTx"` -} - -type transactionBrokernodeCreateReq struct { - CurrentList []string `json:"currentList"` -} - -type transactionBrokernodeCreateRes struct { - ID uuid.UUID `json:"id"` - Pow BrokernodeAddressPow `json:"pow"` -} - -type transactionBrokernodeUpdateReq struct { - Trytes string `json:"trytes"` -} - -type transactionBrokernodeUpdateRes struct { - Purchase string `json:"purchase"` -} - -// Creates a transaction. - -func (usr *TransactionBrokernodeResource) Create(c buffalo.Context) error { - - if os.Getenv("TANGLE_MAINTENANCE") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) - } - - if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) - } - - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionBrokernodeResourceCreate, start) - - req := transactionBrokernodeCreateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - brokernode := models.Brokernode{} - - dataMap, dataMapNotFoundErr := models.GetChunkForWebnodePoW() - - existingAddresses := oyster_utils.StringsJoin(req.CurrentList, oyster_utils.StringsJoinDelim) - brokernodeNotFoundErr := models.DB.Where("address NOT IN (?)", existingAddresses).First(&brokernode) - - // DB results error if First() does not return any error. - if dataMapNotFoundErr != nil { - return c.Render(403, r.JSON(map[string]string{"error": "Cannot give proof of work because: " + - dataMapNotFoundErr.Error()})) - } - - // DB results error if First() does not return any error. - if brokernodeNotFoundErr != nil { - return c.Render(403, r.JSON(map[string]string{"error": "No brokernode addresses to sell"})) - } - - tips, err := IotaWrapper.GetTransactionsToApprove() - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - } - - dataMapKey := oyster_utils.GetBadgerKey([]string{dataMap.GenesisHash, strconv.FormatInt(dataMap.Idx, 10)}) - - transaction := models.Transaction{ - Type: models.TransactionTypeBrokernode, - Status: models.TransactionStatusPending, - DataMapID: dataMapKey, - GenesisHash: dataMap.GenesisHash, - Idx: dataMap.Idx, - Purchase: brokernode.Address, - } - - err = models.DB.Transaction(func(tx *pop.Connection) error { - - vErr, err := tx.ValidateAndCreate(&transaction) - if err != nil || vErr.HasAny() { - return fmt.Errorf("Unable to Save Transaction: %v, %v", vErr, err) - } - return nil - }) - if err != nil { - oyster_utils.LogIfError(err, nil) - return c.Error(400, err) - } - - res := transactionBrokernodeCreateRes{ - ID: transaction.ID, - Pow: BrokernodeAddressPow{ - Address: dataMap.Address, - Message: dataMap.Message, - BranchTx: string(tips.BranchTransaction), - TrunkTx: string(tips.TrunkTransaction), - }, - } - - return c.Render(200, r.JSON(res)) -} - -func (usr *TransactionBrokernodeResource) Update(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionBrokernodeResourceUpdate, start) - - req := transactionBrokernodeUpdateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - // Get transaction - t := &models.Transaction{} - transactionError := models.DB.Find(t, c.Param("id")) - - trytes, err := giota.ToTrytes(req.Trytes) - if err != nil { - oyster_utils.LogIfError(err, nil) - return c.Render(400, r.JSON(map[string]string{"error": err.Error()})) - } - iotaTransaction, iotaError := giota.NewTransaction(trytes) - - if transactionError != nil || iotaError != nil { - return c.Render(400, r.JSON(map[string]string{"error": "No transaction found"})) - } - - chunkDataInProgress := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) - chunkDataComplete := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) - - chunkToUse := chunkDataInProgress - if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && - oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { - chunkToUse = chunkDataComplete - } else if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && !oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { - return c.Render(400, r.JSON(map[string]string{"error": "Could not find data for specified chunk"})) - } - - address, addError := giota.ToAddress(chunkToUse.Address) - validAddress := addError == nil && address == iotaTransaction.Address - if !validAddress { - return c.Render(400, r.JSON(map[string]string{"error": "Address is invalid"})) - } - - _, messageErr := giota.ToTrytes(chunkToUse.Message) - validMessage := messageErr == nil && strings.Contains(fmt.Sprint(iotaTransaction.SignatureMessageFragment), - chunkToUse.Message) - if !validMessage { - return c.Render(400, r.JSON(map[string]string{"error": "Message is invalid"})) - } - - host_ip := os.Getenv("HOST_IP") - provider := "http://" + host_ip + ":14265" - iotaAPI := giota.NewAPI(provider, nil) - - iotaTransactions := []giota.Transaction{*iotaTransaction} - broadcastErr := iotaAPI.BroadcastTransactions(iotaTransactions) - - if broadcastErr != nil { - return c.Render(400, r.JSON(map[string]string{"error": "Broadcast to Tangle failed"})) - } - - models.DB.Transaction(func(tx *pop.Connection) error { - t.Status = models.TransactionStatusComplete - tx.ValidateAndSave(t) - - return nil - }) - - res := transactionBrokernodeUpdateRes{Purchase: t.Purchase} - - return c.Render(202, r.JSON(res)) -} diff --git a/actions/v2/transaction_brokernodes_test.go b/actions/v2/transaction_brokernodes_test.go deleted file mode 100644 index 66a517e7..00000000 --- a/actions/v2/transaction_brokernodes_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package actions - -func (suite *ActionSuite) Test_Create_Transaction_brokernodes() {} -func (suite *ActionSuite) Test_Update_Transaction_brokernodes() {} diff --git a/actions/v2/transaction_genesis_hashes.go b/actions/v2/transaction_genesis_hashes.go deleted file mode 100644 index 25a9093d..00000000 --- a/actions/v2/transaction_genesis_hashes.go +++ /dev/null @@ -1,215 +0,0 @@ -package actions - -import ( - "fmt" - "github.com/gobuffalo/buffalo" - "github.com/gobuffalo/pop" - "github.com/gobuffalo/uuid" - "github.com/iotaledger/giota" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/utils" - "math" - "os" - "strconv" - "strings" -) - -type TransactionGenesisHashResource struct { - buffalo.Resource -} - -// Request Response structs -type GenesisHashPow struct { - Address string `json:"address"` - Message string `json:"message"` - BranchTx string `json:"branchTx"` - TrunkTx string `json:"trunkTx"` -} - -type transactionGenesisHashCreateReq struct { - CurrentList []string `json:"currentList"` -} - -type transactionGenesisHashCreateRes struct { - ID uuid.UUID `json:"id"` - Pow GenesisHashPow `json:"pow"` -} - -type transactionGenesisHashUpdateReq struct { - Trytes string `json:"trytes"` -} - -type transactionGenesisHashUpdateRes struct { - Purchase string `json:"purchase"` - NumberOfChunks int `json:"numberOfChunks"` -} - -// Creates a transaction. -func (usr *TransactionGenesisHashResource) Create(c buffalo.Context) error { - - if os.Getenv("TANGLE_MAINTENANCE") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) - } - - if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) - } - - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionGenesisHashResourceCreate, start) - - req := transactionGenesisHashCreateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - storedGenesisHash, genesisHashNotFound := models.GetGenesisHashForWebnode(req.CurrentList) - - if genesisHashNotFound != nil { - return c.Render(403, r.JSON(map[string]string{"error": "No genesis hash available"})) - } - - dataMap, dataMapNotFoundErr := models.GetChunkForWebnodePoW() - - if dataMapNotFoundErr != nil { - return c.Render(403, r.JSON(map[string]string{"error": "Cannot give proof of work because: " + - dataMapNotFoundErr.Error()})) - } - - tips, err := IotaWrapper.GetTransactionsToApprove() - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - } - - dataMapKey := oyster_utils.GetBadgerKey([]string{dataMap.GenesisHash, strconv.FormatInt(dataMap.Idx, 10)}) - - t := models.Transaction{} - models.DB.Transaction(func(tx *pop.Connection) error { - - storedGenesisHash.WebnodeCount++ - if storedGenesisHash.WebnodeCount >= models.WebnodeCountLimit { - storedGenesisHash.Status = models.StoredGenesisHashAssigned - } - vErr, err := tx.ValidateAndSave(&storedGenesisHash) - oyster_utils.LogIfError(err, nil) - oyster_utils.LogIfValidationError("validation errors in transaction_genesis_hashes.", vErr, nil) - - t = models.Transaction{ - Type: models.TransactionTypeGenesisHash, - Status: models.TransactionStatusPending, - DataMapID: dataMapKey, - GenesisHash: dataMap.GenesisHash, - Idx: dataMap.Idx, - Purchase: storedGenesisHash.GenesisHash, - } - tx.ValidateAndSave(&t) - return nil - }) - - res := transactionGenesisHashCreateRes{ - ID: t.ID, - Pow: GenesisHashPow{ - Address: dataMap.Address, - Message: dataMap.Message, - BranchTx: string(tips.BranchTransaction), - TrunkTx: string(tips.TrunkTransaction), - }, - } - - return c.Render(200, r.JSON(res)) -} - -func (usr *TransactionGenesisHashResource) Update(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTransactionGenesisHashResourceUpdate, start) - - req := transactionGenesisHashUpdateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - // Get transaction - t := &models.Transaction{} - transactionError := models.DB.Find(t, c.Param("id")) - if transactionError != nil { - return c.Render(400, r.JSON(map[string]string{"error": "No transaction found"})) - } - - trytes, err := giota.ToTrytes(req.Trytes) - if err != nil { - oyster_utils.LogIfError(err, nil) - return c.Render(400, r.JSON(map[string]string{"error": err.Error()})) - } - iotaTransaction, iotaError := giota.NewTransaction(trytes) - - if iotaError != nil { - return c.Render(400, r.JSON(map[string]string{"error": "Could not generate transaction object from trytes"})) - } - - chunkDataInProgress := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) - chunkDataComplete := models.GetSingleChunkData(oyster_utils.InProgressDir, t.GenesisHash, t.Idx) - - chunkToUse := chunkDataInProgress - if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && - oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { - chunkToUse = chunkDataComplete - - } else if !oyster_utils.AllChunkDataHasArrived(chunkDataInProgress) && !oyster_utils.AllChunkDataHasArrived(chunkDataComplete) { - return c.Render(400, r.JSON(map[string]string{"error": "Could not find data for specified chunk"})) - } - - address, addError := giota.ToAddress(chunkToUse.Address) - - validAddress := addError == nil && address == iotaTransaction.Address - if !validAddress { - return c.Render(400, r.JSON(map[string]string{"error": "Address is invalid"})) - } - - _, messageErr := giota.ToTrytes(chunkToUse.Message) - validMessage := messageErr == nil && strings.Contains(fmt.Sprint(iotaTransaction.SignatureMessageFragment), - chunkToUse.Message) - if !validMessage { - return c.Render(400, r.JSON(map[string]string{"error": "Message is invalid"})) - } - - host_ip := os.Getenv("HOST_IP") - provider := "http://" + host_ip + ":14265" - iotaAPI := giota.NewAPI(provider, nil) - - iotaTransactions := []giota.Transaction{*iotaTransaction} - broadcastErr := iotaAPI.BroadcastTransactions(iotaTransactions) - - if broadcastErr != nil { - return c.Render(400, r.JSON(map[string]string{"error": "Broadcast to Tangle failed"})) - } - - storedGenesisHash := models.StoredGenesisHash{} - genesisHashNotFound := models.DB.Limit(1).Where("genesis_hash = ?", t.Purchase).First(&storedGenesisHash) - - if genesisHashNotFound != nil { - return c.Render(403, r.JSON(map[string]string{"error": "Stored genesis hash was not found"})) - } - - models.DB.Transaction(func(tx *pop.Connection) error { - t.Status = models.TransactionStatusComplete - tx.ValidateAndSave(t) - - storedGenesisHash.Status = models.StoredGenesisHashUnassigned - storedGenesisHash.WebnodeCount = storedGenesisHash.WebnodeCount + 1 - tx.ValidateAndSave(&storedGenesisHash) - - return nil - }) - - res := transactionGenesisHashUpdateRes{ - Purchase: t.Purchase, - NumberOfChunks: int(math.Ceil(float64(storedGenesisHash.FileSizeBytes) / models.FileBytesChunkSize)), - } - - return c.Render(202, r.JSON(res)) -} diff --git a/actions/v2/transaction_genesis_hashes_test.go b/actions/v2/transaction_genesis_hashes_test.go deleted file mode 100644 index 2c01c2ee..00000000 --- a/actions/v2/transaction_genesis_hashes_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package actions - -func (suite *ActionSuite) Test_Create_Transaction_genesis_hashes() {} -func (suite *ActionSuite) Test_Update_Transaction_genesis_hashes() {} diff --git a/actions/v2/treasures.go b/actions/v2/treasures.go deleted file mode 100644 index 71567486..00000000 --- a/actions/v2/treasures.go +++ /dev/null @@ -1,99 +0,0 @@ -package actions - -import ( - "errors" - "fmt" - "os" - - "github.com/ethereum/go-ethereum/crypto" - "github.com/gobuffalo/buffalo" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/utils" -) - -type TreasuresResource struct { - buffalo.Resource -} - -type treasureReq struct { - ReceiverEthAddr string `json:"receiverEthAddr"` - GenesisHash string `json:"genesisHash"` - SectorIdx int `json:"sectorIdx"` - NumChunks int `json:"numChunks"` - EthKey string `json:"ethKey"` -} - -type treasureRes struct { - Success bool `json:"success"` -} - -// Verifies the treasure and claims such treasure. -func (t *TreasuresResource) VerifyAndClaim(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramTreasuresResourceVerifyAndClaim, start) - - req := treasureReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - if req.EthKey == os.Getenv("TEST_MODE_WALLET_KEY") { - return c.Render(200, r.JSON(treasureRes{ - Success: true, - })) - } - - addr := oyster_utils.ComputeSectorDataMapAddress(req.GenesisHash, req.SectorIdx, req.NumChunks) - verify, err := IotaWrapper.VerifyTreasure(addr) - - _, keyErr := crypto.HexToECDSA(req.EthKey) - - if err != nil { - c.Error(400, err) - } - if keyErr != nil { - c.Error(400, keyErr) - } - if !verify { - return c.Render(200, r.JSON(treasureRes{ - Success: verify, - })) - } - - ethAddr := EthWrapper.GenerateEthAddrFromPrivateKey(req.EthKey) - - startingClaimClock, claimClockErr := EthWrapper.CheckClaimClock(ethAddr) - - if startingClaimClock.Int64() == int64(0) { - c.Error(400, errors.New("claim clock should be 1 or a timestamp but received 0")) - } else if claimClockErr != nil { - c.Error(400, claimClockErr) - } else { - webnodeTreasureClaim := models.WebnodeTreasureClaim{ - GenesisHash: req.GenesisHash, - SectorIdx: req.SectorIdx, - NumChunks: req.NumChunks, - ReceiverETHAddr: req.ReceiverEthAddr, - TreasureETHAddr: ethAddr.String(), - TreasureETHPrivateKey: req.EthKey, - StartingClaimClock: startingClaimClock.Int64(), - } - - vErr, err := models.DB.ValidateAndCreate(&webnodeTreasureClaim) - - oyster_utils.LogIfError(errors.New(vErr.Error()), nil) - oyster_utils.LogIfError(err, nil) - - verify = err == nil && len(vErr.Errors) == 0 - } - - res := treasureRes{ - Success: verify && - startingClaimClock.Int64() != int64(0) && - claimClockErr == nil, - } - - return c.Render(200, r.JSON(res)) -} diff --git a/actions/v2/treasures_test.go b/actions/v2/treasures_test.go deleted file mode 100644 index cd062428..00000000 --- a/actions/v2/treasures_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package actions - -import ( - "encoding/json" - "errors" - "github.com/ethereum/go-ethereum/common" - "github.com/oysterprotocol/brokernode/services" - "io/ioutil" - "math/big" -) - -// Record data for VerifyTreasure method -type mockVerifyTreasure struct { - hasCalled bool - input_addr []string - output_bool bool - output_error error -} - -var checkClaimClockCalled = false -var ethAddressCalledWithCheckClaimClock common.Address - -func (suite *ActionSuite) Test_VerifyTreasureAndClaim_Success() { - checkClaimClockCalled = false - - mockVerifyTreasure := mockVerifyTreasure{ - output_bool: true, - output_error: nil, - } - IotaWrapper = services.IotaService{ - VerifyTreasure: mockVerifyTreasure.verifyTreasure, - } - EthWrapper = services.Eth{ - GenerateEthAddrFromPrivateKey: EthWrapper.GenerateEthAddrFromPrivateKey, - CheckClaimClock: func(address common.Address) (*big.Int, error) { - checkClaimClockCalled = true - ethAddressCalledWithCheckClaimClock = address - return big.NewInt(1), nil - }, - } - - ethKey := "9999999999999999999999999999999999999999999999999999999999999991" - addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) - - res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ - "receiverEthAddr": addr, - "genesisHash": "1234", - "sectorIdx": 1, - "numChunks": 5, - "ethKey": ethKey, - }) - - suite.Equal(200, res.Code) - - // Check mockVerifyTreasure - suite.True(mockVerifyTreasure.hasCalled) - suite.Equal(5, len(mockVerifyTreasure.input_addr)) - - suite.Equal(addr, ethAddressCalledWithCheckClaimClock) - suite.Equal(true, checkClaimClockCalled) - - // Parse response - resParsed := treasureRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(true, resParsed.Success) -} - -func (suite *ActionSuite) Test_VerifyTreasure_FailureWithError() { - - checkClaimClockCalled = false - - m := mockVerifyTreasure{ - output_bool: false, - output_error: errors.New("Invalid address"), - } - IotaWrapper = services.IotaService{ - VerifyTreasure: m.verifyTreasure, - } - - ethKey := "9999999999999999999999999999999999999999999999999999999999999991" - addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) - - res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ - "receiverEthAddr": addr, - "genesisHash": "1234", - "sectorIdx": 1, - "numChunks": 5, - }) - - suite.True(m.hasCalled) - suite.Equal(5, len(m.input_addr)) - - // Parse response - resParsed := treasureRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(false, resParsed.Success) -} - -func (suite *ActionSuite) Test_Check_Claim_Clock_Error() { - - checkClaimClockCalled = false - - mockVerifyTreasure := mockVerifyTreasure{ - output_bool: true, - output_error: nil, - } - IotaWrapper = services.IotaService{ - VerifyTreasure: mockVerifyTreasure.verifyTreasure, - } - EthWrapper = services.Eth{ - GenerateEthAddrFromPrivateKey: EthWrapper.GenerateEthAddrFromPrivateKey, - CheckClaimClock: func(address common.Address) (*big.Int, error) { - ethAddressCalledWithCheckClaimClock = address - checkClaimClockCalled = true - return big.NewInt(-1), errors.New("error") - }, - } - - ethKey := "9999999999999999999999999999999999999999999999999999999999999991" - addr := services.EthWrapper.GenerateEthAddrFromPrivateKey(ethKey) - - res := suite.JSON("/api/v2/treasures").Post(map[string]interface{}{ - "receiverEthAddr": addr, - "genesisHash": "1234", - "sectorIdx": 1, - "numChunks": 5, - "ethKey": ethKey, - }) - - suite.Equal(200, res.Code) - - suite.True(mockVerifyTreasure.hasCalled) - suite.True(checkClaimClockCalled) - - // Parse response - resParsed := treasureRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(false, resParsed.Success) -} - -// For mocking VerifyTreasure method -func (v *mockVerifyTreasure) verifyTreasure(addr []string) (bool, error) { - v.hasCalled = true - v.input_addr = addr - return v.output_bool, v.output_error -} diff --git a/actions/v2/upload_sessions.go b/actions/v2/upload_sessions.go deleted file mode 100644 index af121296..00000000 --- a/actions/v2/upload_sessions.go +++ /dev/null @@ -1,465 +0,0 @@ -package actions - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - "strconv" - "time" - - "github.com/gobuffalo/buffalo" - "github.com/gobuffalo/pop/nulls" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/services" - "github.com/oysterprotocol/brokernode/utils" - "github.com/pkg/errors" - "gopkg.in/segmentio/analytics-go.v3" -) - -type UploadSessionResource struct { - buffalo.Resource -} - -// Request Response structs - -type uploadSessionCreateReq struct { - GenesisHash string `json:"genesisHash"` - NumChunks int `json:"numChunks"` - FileSizeBytes uint64 `json:"fileSizeBytes"` // This is Trytes instead of Byte - BetaIP string `json:"betaIp"` - StorageLengthInYears int `json:"storageLengthInYears"` - AlphaTreasureIndexes []int `json:"alphaTreasureIndexes"` - Invoice models.Invoice `json:"invoice"` - Version uint32 `json:"version"` -} - -type uploadSessionCreateRes struct { - ID string `json:"id"` - UploadSession models.UploadSession `json:"uploadSession"` - BetaSessionID string `json:"betaSessionId"` - Invoice models.Invoice `json:"invoice"` -} - -type uploadSessionCreateBetaRes struct { - ID string `json:"id"` - UploadSession models.UploadSession `json:"uploadSession"` - BetaSessionID string `json:"betaSessionId"` - Invoice models.Invoice `json:"invoice"` - BetaTreasureIndexes []int `json:"betaTreasureIndexes"` -} - -type UploadSessionUpdateReq struct { - Chunks []models.ChunkReq `json:"chunks"` -} - -type paymentStatusCreateRes struct { - ID string `json:"id"` - PaymentStatus string `json:"paymentStatus"` -} - -var NumChunksLimit = -1 //unlimited - -func init() { - -} - -// Create creates an upload session. -func (usr *UploadSessionResource) Create(c buffalo.Context) error { - - if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { - err := errors.New("Deployment in progress. Try again later") - fmt.Println(err) - c.Error(400, err) - return err - } - - if v, err := strconv.Atoi(os.Getenv("NUM_CHUNKS_LIMIT")); err == nil { - NumChunksLimit = v - } - - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreate, start) - - req := uploadSessionCreateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - if NumChunksLimit != -1 && req.NumChunks > NumChunksLimit { - err := errors.New("This broker has a limit of " + fmt.Sprint(NumChunksLimit) + " file chunks.") - fmt.Println(err) - c.Error(400, err) - return err - } - - alphaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() - - // Start Alpha Session. - alphaSession := models.UploadSession{ - Type: models.SessionTypeAlpha, - GenesisHash: req.GenesisHash, - FileSizeBytes: req.FileSizeBytes, - NumChunks: req.NumChunks, - StorageLengthInYears: req.StorageLengthInYears, - ETHAddrAlpha: nulls.NewString(alphaEthAddr.Hex()), - ETHPrivateKey: privKey, - Version: req.Version, - } - - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_alpha_session", analytics.NewProperties(). - Set("id", alphaSession.ID). - Set("genesis_hash", alphaSession.GenesisHash). - Set("file_size_byes", alphaSession.FileSizeBytes). - Set("num_chunks", alphaSession.NumChunks). - Set("storage_years", alphaSession.StorageLengthInYears)) - - if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { - - dbID := []string{oyster_utils.InProgressDir, alphaSession.GenesisHash, oyster_utils.HashDir} - - db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) - if db == nil { - err := errors.New("error creating unique badger DB for hashes") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - } - - vErr, err := alphaSession.StartUploadSession() - if err != nil || vErr.HasAny() { - err = fmt.Errorf("StartUploadSession error: %v and validation error: %v", err, vErr) - c.Error(400, err) - return err - } - - invoice := alphaSession.GetInvoice() - - // Mutates this because copying in golang sucks... - req.Invoice = invoice - - req.AlphaTreasureIndexes = oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) - - // Start Beta Session. - var betaSessionID = "" - var betaTreasureIndexes []int - hasBeta := req.BetaIP != "" - if hasBeta { - betaReq, err := json.Marshal(req) - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - reqBetaBody := bytes.NewBuffer(betaReq) - - // Should we be hardcoding the port? - betaURL := req.BetaIP + ":3000/api/v2/upload-sessions/beta" - betaRes, err := http.Post(betaURL, "application/json", reqBetaBody) - defer betaRes.Body.Close() // we need to close the connection - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - betaSessionRes := &uploadSessionCreateBetaRes{} - if err := oyster_utils.ParseResBody(betaRes, betaSessionRes); err != nil { - err = fmt.Errorf("Unable to communicate with Beta node: %v", err) - // This should consider as BadRequest since the client pick the beta node. - c.Error(400, err) - return err - } - - betaSessionID = betaSessionRes.ID - - betaTreasureIndexes = betaSessionRes.BetaTreasureIndexes - alphaSession.ETHAddrBeta = betaSessionRes.UploadSession.ETHAddrBeta - } - - if err := models.DB.Save(&alphaSession); err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - models.NewBrokerBrokerTransaction(&alphaSession) - - if hasBeta { - mergedIndexes, _ := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, - oyster_utils.FileSectorInChunkSize, req.NumChunks) - - if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { - err := errors.New("no indexes selected for treasure") - fmt.Println(err) - c.Error(400, err) - return err - } - - for { - privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) - if err != nil { - err := errors.New("Could not generate eth keys: " + err.Error()) - fmt.Println(err) - c.Error(400, err) - return err - } - if len(mergedIndexes) != len(privateKeys) { - err := errors.New("privateKeys and mergedIndexes should have the same length") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - // Update alpha treasure idx map. - alphaSession.MakeTreasureIdxMap(mergedIndexes, privateKeys) - - treasureIndexes, _ := alphaSession.GetTreasureIndexes() - - if alphaSession.TreasureStatus == models.TreasureInDataMapPending && - alphaSession.TreasureIdxMap.Valid && alphaSession.TreasureIdxMap.String != "" && - len(treasureIndexes) == len(mergedIndexes) { - models.DB.ValidateAndUpdate(&alphaSession) - break - } - } - } - - res := uploadSessionCreateRes{ - UploadSession: alphaSession, - ID: alphaSession.ID.String(), - BetaSessionID: betaSessionID, - Invoice: invoice, - } - //go waitForTransferAndNotifyBeta( - // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) - - return c.Render(200, r.JSON(res)) -} - -// Update uploads a chunk associated with an upload session. -func (usr *UploadSessionResource) Update(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceUpdate, start) - - req := UploadSessionUpdateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - // Get session - uploadSession := &models.UploadSession{} - err := models.DB.Find(uploadSession, c.Param("id")) - - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: updating_session", analytics.NewProperties(). - Set("id", uploadSession.ID). - Set("genesis_hash", uploadSession.GenesisHash). - Set("file_size_byes", uploadSession.FileSizeBytes). - Set("num_chunks", uploadSession.NumChunks). - Set("storage_years", uploadSession.StorageLengthInYears)) - - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - if uploadSession == nil { - err := errors.New("Error finding sessions") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - treasureIdxMap, err := uploadSession.GetTreasureIndexes() - - if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { - dbID := []string{oyster_utils.InProgressDir, uploadSession.GenesisHash, oyster_utils.MessageDir} - - db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) - if db == nil { - err := errors.New("error creating unique badger DB for messages") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - } - - // Update dMaps to have chunks async - go func() { - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: async_datamap_updates", analytics.NewProperties(). - Set("id", uploadSession.ID). - Set("genesis_hash", uploadSession.GenesisHash). - Set("file_size_byes", uploadSession.FileSizeBytes). - Set("num_chunks", uploadSession.NumChunks). - Set("storage_years", uploadSession.StorageLengthInYears)) - - models.ProcessAndStoreChunkData(req.Chunks, uploadSession.GenesisHash, treasureIdxMap, - models.DataMapsTimeToLive) - }() - - return c.Render(202, r.JSON(map[string]bool{"success": true})) -} - -// CreateBeta creates an upload session on the beta broker. -func (usr *UploadSessionResource) CreateBeta(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreateBeta, start) - - req := uploadSessionCreateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - betaTreasureIndexes := oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) - - // Generates ETH address. - betaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() - - u := models.UploadSession{ - Type: models.SessionTypeBeta, - GenesisHash: req.GenesisHash, - NumChunks: req.NumChunks, - FileSizeBytes: req.FileSizeBytes, - StorageLengthInYears: req.StorageLengthInYears, - TotalCost: req.Invoice.Cost, - ETHAddrAlpha: req.Invoice.EthAddress, - ETHAddrBeta: nulls.NewString(betaEthAddr.Hex()), - ETHPrivateKey: privKey, - Version: req.Version, - } - - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_beta_session", analytics.NewProperties(). - Set("id", u.ID). - Set("genesis_hash", u.GenesisHash). - Set("file_size_byes", u.FileSizeBytes). - Set("num_chunks", u.NumChunks). - Set("storage_years", u.StorageLengthInYears)) - - if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { - dbID := []string{oyster_utils.InProgressDir, u.GenesisHash, oyster_utils.HashDir} - - db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) - if db == nil { - err := errors.New("error creating unique badger DB for hashes") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - } - - vErr, err := u.StartUploadSession() - - if err != nil || vErr.HasAny() { - err = fmt.Errorf("Can't startUploadSession with validation error: %v and err: %v", vErr, err) - c.Error(400, err) - return err - } - - if len(vErr.Errors) > 0 { - c.Render(422, r.JSON(vErr.Errors)) - return err - } - - mergedIndexes, err := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, - oyster_utils.FileSectorInChunkSize, req.NumChunks) - - if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { - err := errors.New("no indexes selected for treasure") - fmt.Println(err) - c.Error(400, err) - return err - } - - if err != nil { - fmt.Println(err) - c.Error(400, err) - return err - } - for { - privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) - if err != nil { - err := errors.New("Could not generate eth keys: " + err.Error()) - fmt.Println(err) - c.Error(400, err) - return err - } - if len(mergedIndexes) != len(privateKeys) { - err := errors.New("privateKeys and mergedIndexes should have the same length") - fmt.Println(err) - c.Error(400, err) - return err - } - u.MakeTreasureIdxMap(mergedIndexes, privateKeys) - - treasureIndexes, err := u.GetTreasureIndexes() - - if u.TreasureStatus == models.TreasureInDataMapPending && - u.TreasureIdxMap.Valid && u.TreasureIdxMap.String != "" && - len(treasureIndexes) == len(mergedIndexes) { - models.DB.ValidateAndUpdate(&u) - break - } - } - - models.NewBrokerBrokerTransaction(&u) - - res := uploadSessionCreateBetaRes{ - UploadSession: u, - ID: u.ID.String(), - Invoice: u.GetInvoice(), - BetaTreasureIndexes: betaTreasureIndexes, - } - //go waitForTransferAndNotifyBeta( - // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) - - return c.Render(200, r.JSON(res)) -} - -func (usr *UploadSessionResource) GetPaymentStatus(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceGetPaymentStatus, start) - - session := models.UploadSession{} - err := models.DB.Find(&session, c.Param("id")) - - if err != nil { - c.Error(400, err) - oyster_utils.LogIfError(err, nil) - return err - } - if (session == models.UploadSession{}) { - err := errors.New("Did not find session that matched id" + c.Param("id")) - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - // Force to check the status - if session.PaymentStatus != models.PaymentStatusConfirmed { - balance := EthWrapper.CheckPRLBalance(services.StringToAddress(session.ETHAddrAlpha.String)) - if balance.Int64() > 0 { - previousPaymentStatus := session.PaymentStatus - session.PaymentStatus = models.PaymentStatusConfirmed - err = models.DB.Save(&session) - if err != nil { - session.PaymentStatus = previousPaymentStatus - } else { - models.SetBrokerTransactionToPaid(session) - } - } - } - - res := paymentStatusCreateRes{ - ID: session.ID.String(), - PaymentStatus: session.GetPaymentStatus(), - } - - return c.Render(200, r.JSON(res)) -} diff --git a/actions/v2/upload_sessions_test.go b/actions/v2/upload_sessions_test.go deleted file mode 100644 index 98f58f50..00000000 --- a/actions/v2/upload_sessions_test.go +++ /dev/null @@ -1,331 +0,0 @@ -package actions - -import ( - "encoding/json" - "fmt" - "github.com/oysterprotocol/brokernode/utils" - "io/ioutil" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/gobuffalo/pop/nulls" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/services" -) - -type mockWaitForTransfer struct { - hasCalled bool - input_brokerAddr common.Address - output_int *big.Int - output_error error -} - -type mockSendPrl struct { - hasCalled bool - input_msg services.OysterCallMsg - output_bool bool -} - -type mockCheckPRLBalance struct { - hasCalled bool - input_addr common.Address - output_int *big.Int -} - -func (suite *ActionSuite) Test_UploadSessionsCreate() { - mockWaitForTransfer := mockWaitForTransfer{ - output_error: nil, - output_int: big.NewInt(100), - } - mockSendPrl := mockSendPrl{ - output_bool: true, - } - EthWrapper = services.Eth{ - WaitForTransfer: mockWaitForTransfer.waitForTransfer, - SendPRL: mockSendPrl.sendPrl, - GenerateEthAddr: services.EthWrapper.GenerateEthAddr, - GenerateKeys: services.EthWrapper.GenerateKeys, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - res := suite.JSON("/api/v2/upload-sessions").Post(map[string]interface{}{ - "genesisHash": genHash, - "fileSizeBytes": 123, - "numChunks": 2, - "storageLengthInYears": 1, - }) - - // Parse response - resParsed := uploadSessionCreateRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(200, res.Code) - suite.Equal(genHash, resParsed.UploadSession.GenesisHash) - suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) - suite.Equal(models.SessionTypeAlpha, resParsed.UploadSession.Type) - suite.NotEqual(0, resParsed.Invoice.Cost) - suite.NotEqual("", resParsed.Invoice.EthAddress) - - time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. - - // TODO: fix waitForTransfer and uncomment it out in - // actions/upload_sessions.go then uncomment out these tests. - //suite.True(mockWaitForTransfer.hasCalled) - //suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) - - // mockCheckPRLBalance will result a positive value, and Alpha knows that beta has such balance, it won't send - // it again. - suite.False(mockSendPrl.hasCalled) - - // TODO: fix waitForTransfer and uncomment it out in - // actions/upload_sessions.go then uncomment out these tests. - // verifyPaymentConfirmation(as, resParsed.ID) - - chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) - - suite.Equal(genHash, chunkData.Hash) - - brokerTx := []models.BrokerBrokerTransaction{} - - suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) - - suite.Equal(1, len(brokerTx)) -} - -func (suite *ActionSuite) Test_UploadSessionsCreateBeta() { - mockWaitForTransfer := mockWaitForTransfer{ - output_error: nil, - output_int: big.NewInt(100), - } - mockSendPrl := mockSendPrl{} - - EthWrapper = services.Eth{ - WaitForTransfer: mockWaitForTransfer.waitForTransfer, - SendPRL: mockSendPrl.sendPrl, - GenerateEthAddr: services.EthWrapper.GenerateEthAddr, - GenerateKeys: services.EthWrapper.GenerateKeys, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - res := suite.JSON("/api/v2/upload-sessions/beta").Post(map[string]interface{}{ - "genesisHash": genHash, - "fileSizeBytes": 123, - "numChunks": 2, - "storageLengthInYears": 1, - "alphaTreasureIndexes": []int{1}, - }) - - // Parse response - resParsed := uploadSessionCreateBetaRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - err = json.Unmarshal(bodyBytes, &resParsed) - suite.Nil(err) - - suite.Equal(200, res.Code) - suite.Equal(genHash, resParsed.UploadSession.GenesisHash) - suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) - suite.Equal(models.SessionTypeBeta, resParsed.UploadSession.Type) - suite.Equal(1, len(resParsed.BetaTreasureIndexes)) - suite.NotEqual(0, resParsed.Invoice.Cost) - suite.NotEqual("", resParsed.Invoice.EthAddress) - - time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. - - // TODO: fix waitForTransfer and uncomment it out in - // actions/upload_sessions.go then uncomment out this test. - //suite.True(mockWaitForTransfer.hasCalled) - suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) - suite.False(mockSendPrl.hasCalled) - - // TODO: fix waitForTransfer and uncomment it out in - // actions/upload_sessions.go then uncomment out these tests. - // verifyPaymentConfirmation(as, resParsed.ID) - - chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) - - suite.Equal(genHash, chunkData.Hash) - - brokerTx := []models.BrokerBrokerTransaction{} - - suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) - - suite.Equal(1, len(brokerTx)) -} - -func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_Paid() { - //setup - mockCheckPRLBalance := mockCheckPRLBalance{} - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusConfirmed, - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("confirmed", resParsed.PaymentStatus) - suite.False(mockCheckPRLBalance.hasCalled) -} - -func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_NoConfirmButCheckComplete() { - //setup - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(10), - } - mockSendPrl := mockSendPrl{} - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - SendPRL: mockSendPrl.sendPrl, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusPending, - ETHAddrAlpha: nulls.NewString("alpha"), - ETHAddrBeta: nulls.NewString("beta"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("confirmed", resParsed.PaymentStatus) - - /* checkPRLBalance has been called once just for alpha. Sending - to beta now occurs in a job. */ - suite.True(mockCheckPRLBalance.hasCalled) - suite.False(mockSendPrl.hasCalled) - suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_NoConfirmAndCheckIncomplete() { - //setup - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(0), - } - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusInvoiced, - ETHAddrAlpha: nulls.NewString("alpha"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("invoiced", resParsed.PaymentStatus) - suite.True(mockCheckPRLBalance.hasCalled) - suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusInvoiced, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_BetaConfirmed() { - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(10), - } - mockSendPrl := mockSendPrl{} - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - SendPRL: mockSendPrl.sendPrl, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - Type: models.SessionTypeBeta, - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusInvoiced, - ETHAddrAlpha: nulls.NewString("alpha"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("confirmed", resParsed.PaymentStatus) - suite.True(mockCheckPRLBalance.hasCalled) - suite.False(mockSendPrl.hasCalled) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsGetPaymentStatus_DoesntExist() { - //res := suite.JSON("/api/v2/upload-sessions/" + "noIDFound").Get() - - //TODO: Return better error response when ID does not exist -} - -func getPaymentStatus(seededUploadSession models.UploadSession, suite *ActionSuite) paymentStatusCreateRes { - seededUploadSession.StartUploadSession() - - session := models.UploadSession{} - suite.Nil(suite.DB.Where("genesis_hash = ?", seededUploadSession.GenesisHash).First(&session)) - - //execute method - res := suite.JSON("/api/v2/upload-sessions/" + fmt.Sprint(session.ID)).Get() - - // Parse response - resParsed := paymentStatusCreateRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - - suite.Nil(json.Unmarshal(bodyBytes, &resParsed)) - - return resParsed -} - -func verifyPaymentConfirmation(sessionId string, suite *ActionSuite) { - session := models.UploadSession{} - err := suite.DB.Find(&session, sessionId) - suite.Nil(err) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -} - -func (v *mockWaitForTransfer) waitForTransfer(brokerAddr common.Address, transferType string) (*big.Int, error) { - v.hasCalled = true - v.input_brokerAddr = brokerAddr - return v.output_int, v.output_error -} - -func (v *mockSendPrl) sendPrl(msg services.OysterCallMsg) bool { - v.hasCalled = true - v.input_msg = msg - return v.output_bool -} - -func (v *mockCheckPRLBalance) checkPRLBalance(addr common.Address) *big.Int { - v.hasCalled = true - v.input_addr = addr - return v.output_int -} diff --git a/actions/v2/webnodes.go b/actions/v2/webnodes.go deleted file mode 100644 index 3037f1a1..00000000 --- a/actions/v2/webnodes.go +++ /dev/null @@ -1,56 +0,0 @@ -package actions - -import ( - "fmt" - "os" - - "github.com/gobuffalo/buffalo" - "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/utils" -) - -type WebnodeResource struct { - buffalo.Resource -} - -// Request Response structs - -type webnodeCreateReq struct { - Address string `json:"address"` -} - -type webnodeCreateRes struct { - Webnode models.Webnode `json:"id"` -} - -// Creates a webnode. -func (usr *WebnodeResource) Create(c buffalo.Context) error { - - if os.Getenv("TANGLE_MAINTENANCE") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "This broker is undergoing tangle maintenance"})) - } - - if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { - return c.Render(403, r.JSON(map[string]string{"error": "Deployment in progress. Try again later"})) - } - - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramWebnodeResourceCreate, start) - - req := webnodeCreateReq{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - w := models.Webnode{ - Address: req.Address, - } - - res := webnodeCreateRes{ - Webnode: w, - } - - return c.Render(200, r.JSON(res)) -} From 1383b70af4a09b0f552034b9660c482a4606f548 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Wed, 3 Oct 2018 10:59:20 -0400 Subject: [PATCH 05/13] fix imports --- actions/upload_sessions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/upload_sessions.go b/actions/upload_sessions.go index af121296..995cb8a4 100644 --- a/actions/upload_sessions.go +++ b/actions/upload_sessions.go @@ -13,9 +13,9 @@ import ( "github.com/gobuffalo/pop/nulls" "github.com/oysterprotocol/brokernode/models" "github.com/oysterprotocol/brokernode/services" - "github.com/oysterprotocol/brokernode/utils" + oyster_utils "github.com/oysterprotocol/brokernode/utils" "github.com/pkg/errors" - "gopkg.in/segmentio/analytics-go.v3" + analytics "gopkg.in/segmentio/analytics-go.v3" ) type UploadSessionResource struct { From 9df8cde918ce9c7878880f1011a48157ccfaabf0 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Wed, 3 Oct 2018 15:14:07 -0400 Subject: [PATCH 06/13] data in s3 mode --- utils/mode.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/mode.go b/utils/mode.go index 63c99e46..d26edbf5 100644 --- a/utils/mode.go +++ b/utils/mode.go @@ -48,6 +48,8 @@ const ( DataMapsInSQL DataMapsStorageStatus = iota + 1 /*DataMapsInBadger is when the data maps are stored in badger*/ DataMapsInBadger + /*DataMapsInS3 is when the data maps are stored in S3*/ + DataMapsInS3 ) /*BrokerMode - the mode the broker is in (prod, dummy treasure, etc.)*/ From 9c9372132df141b76dbee2fcd68182b262895af2 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Wed, 3 Oct 2018 15:21:05 -0400 Subject: [PATCH 07/13] v3 of upload sessions (needs tests and routes) --- actions/upload_sessions_v3.go | 465 ++++++++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 actions/upload_sessions_v3.go diff --git a/actions/upload_sessions_v3.go b/actions/upload_sessions_v3.go new file mode 100644 index 00000000..a8ec4a55 --- /dev/null +++ b/actions/upload_sessions_v3.go @@ -0,0 +1,465 @@ +package actions + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "time" + + "github.com/gobuffalo/buffalo" + "github.com/gobuffalo/pop/nulls" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/services" + oyster_utils "github.com/oysterprotocol/brokernode/utils" + "github.com/pkg/errors" + analytics "gopkg.in/segmentio/analytics-go.v3" +) + +type UploadSessionResourceV3 struct { + buffalo.Resource +} + +// Request Response structs + +type uploadSessionCreateReqV3 struct { + GenesisHash string `json:"genesisHash"` + NumChunks int `json:"numChunks"` + FileSizeBytes uint64 `json:"fileSizeBytes"` // This is Trytes instead of Byte + BetaIP string `json:"betaIp"` + StorageLengthInYears int `json:"storageLengthInYears"` + AlphaTreasureIndexes []int `json:"alphaTreasureIndexes"` + Invoice models.Invoice `json:"invoice"` + Version uint32 `json:"version"` +} + +type uploadSessionCreateResV3 struct { + ID string `json:"id"` + UploadSession models.UploadSession `json:"uploadSession"` + BetaSessionID string `json:"betaSessionId"` + Invoice models.Invoice `json:"invoice"` +} + +type uploadSessionCreateBetaResV3 struct { + ID string `json:"id"` + UploadSession models.UploadSession `json:"uploadSession"` + BetaSessionID string `json:"betaSessionId"` + Invoice models.Invoice `json:"invoice"` + BetaTreasureIndexes []int `json:"betaTreasureIndexes"` +} + +type UploadSessionUpdateReqV3 struct { + Chunks []models.ChunkReq `json:"chunks"` +} + +type paymentStatusCreateResV3 struct { + ID string `json:"id"` + PaymentStatus string `json:"paymentStatus"` +} + +var NumChunksLimitV3 = -1 //unlimited + +func init() { + +} + +// Create creates an upload session. +func (usr *UploadSessionResourceV3) Create(c buffalo.Context) error { + + if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { + err := errors.New("Deployment in progress. Try again later") + fmt.Println(err) + c.Error(400, err) + return err + } + + if v, err := strconv.Atoi(os.Getenv("NUM_CHUNKS_LIMIT")); err == nil { + NumChunksLimitV3 = v + } + + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreate, start) + + req := uploadSessionCreateReqV3{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + if NumChunksLimitV3 != -1 && req.NumChunks > NumChunksLimitV3 { + err := errors.New("This broker has a limit of " + fmt.Sprint(NumChunksLimitV3) + " file chunks.") + fmt.Println(err) + c.Error(400, err) + return err + } + + alphaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() + + // Start Alpha Session. + alphaSession := models.UploadSession{ + Type: models.SessionTypeAlpha, + GenesisHash: req.GenesisHash, + FileSizeBytes: req.FileSizeBytes, + NumChunks: req.NumChunks, + StorageLengthInYears: req.StorageLengthInYears, + ETHAddrAlpha: nulls.NewString(alphaEthAddr.Hex()), + ETHPrivateKey: privKey, + Version: req.Version, + } + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_alpha_session", analytics.NewProperties(). + Set("id", alphaSession.ID). + Set("genesis_hash", alphaSession.GenesisHash). + Set("file_size_byes", alphaSession.FileSizeBytes). + Set("num_chunks", alphaSession.NumChunks). + Set("storage_years", alphaSession.StorageLengthInYears)) + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + + dbID := []string{oyster_utils.InProgressDir, alphaSession.GenesisHash, oyster_utils.HashDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for hashes") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + vErr, err := alphaSession.StartUploadSession() + if err != nil || vErr.HasAny() { + err = fmt.Errorf("StartUploadSession error: %v and validation error: %v", err, vErr) + c.Error(400, err) + return err + } + + invoice := alphaSession.GetInvoice() + + // Mutates this because copying in golang sucks... + req.Invoice = invoice + + req.AlphaTreasureIndexes = oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) + + // Start Beta Session. + var betaSessionID = "" + var betaTreasureIndexes []int + hasBeta := req.BetaIP != "" + if hasBeta { + betaReq, err := json.Marshal(req) + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + reqBetaBody := bytes.NewBuffer(betaReq) + + // Should we be hardcoding the port? + betaURL := req.BetaIP + ":3000/api/v2/upload-sessions/beta" + betaRes, err := http.Post(betaURL, "application/json", reqBetaBody) + defer betaRes.Body.Close() // we need to close the connection + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + betaSessionRes := &uploadSessionCreateBetaResV3{} + if err := oyster_utils.ParseResBody(betaRes, betaSessionRes); err != nil { + err = fmt.Errorf("Unable to communicate with Beta node: %v", err) + // This should consider as BadRequest since the client pick the beta node. + c.Error(400, err) + return err + } + + betaSessionID = betaSessionRes.ID + + betaTreasureIndexes = betaSessionRes.BetaTreasureIndexes + alphaSession.ETHAddrBeta = betaSessionRes.UploadSession.ETHAddrBeta + } + + if err := models.DB.Save(&alphaSession); err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + models.NewBrokerBrokerTransaction(&alphaSession) + + if hasBeta { + mergedIndexes, _ := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, + oyster_utils.FileSectorInChunkSize, req.NumChunks) + + if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { + err := errors.New("no indexes selected for treasure") + fmt.Println(err) + c.Error(400, err) + return err + } + + for { + privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) + if err != nil { + err := errors.New("Could not generate eth keys: " + err.Error()) + fmt.Println(err) + c.Error(400, err) + return err + } + if len(mergedIndexes) != len(privateKeys) { + err := errors.New("privateKeys and mergedIndexes should have the same length") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + // Update alpha treasure idx map. + alphaSession.MakeTreasureIdxMap(mergedIndexes, privateKeys) + + treasureIndexes, _ := alphaSession.GetTreasureIndexes() + + if alphaSession.TreasureStatus == models.TreasureInDataMapPending && + alphaSession.TreasureIdxMap.Valid && alphaSession.TreasureIdxMap.String != "" && + len(treasureIndexes) == len(mergedIndexes) { + models.DB.ValidateAndUpdate(&alphaSession) + break + } + } + } + + res := uploadSessionCreateResV3{ + UploadSession: alphaSession, + ID: alphaSession.ID.String(), + BetaSessionID: betaSessionID, + Invoice: invoice, + } + //go waitForTransferAndNotifyBeta( + // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) + + return c.Render(200, r.JSON(res)) +} + +// Update uploads a chunk associated with an upload session. +func (usr *UploadSessionResourceV3) Update(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceUpdate, start) + + req := UploadSessionUpdateReqV3{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + // Get session + uploadSession := &models.UploadSession{} + err := models.DB.Find(uploadSession, c.Param("id")) + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: updating_session", analytics.NewProperties(). + Set("id", uploadSession.ID). + Set("genesis_hash", uploadSession.GenesisHash). + Set("file_size_byes", uploadSession.FileSizeBytes). + Set("num_chunks", uploadSession.NumChunks). + Set("storage_years", uploadSession.StorageLengthInYears)) + + if err != nil { + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + if uploadSession == nil { + err := errors.New("Error finding sessions") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + treasureIdxMap, err := uploadSession.GetTreasureIndexes() + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + dbID := []string{oyster_utils.InProgressDir, uploadSession.GenesisHash, oyster_utils.MessageDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for messages") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + // Update dMaps to have chunks async + go func() { + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: async_datamap_updates", analytics.NewProperties(). + Set("id", uploadSession.ID). + Set("genesis_hash", uploadSession.GenesisHash). + Set("file_size_byes", uploadSession.FileSizeBytes). + Set("num_chunks", uploadSession.NumChunks). + Set("storage_years", uploadSession.StorageLengthInYears)) + + models.ProcessAndStoreChunkData(req.Chunks, uploadSession.GenesisHash, treasureIdxMap, + models.DataMapsTimeToLive) + }() + + return c.Render(202, r.JSON(map[string]bool{"success": true})) +} + +// CreateBeta creates an upload session on the beta broker. +func (usr *UploadSessionResourceV3) CreateBeta(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreateBeta, start) + + req := uploadSessionCreateReqV3{} + if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { + err = fmt.Errorf("Invalid request, unable to parse request body %v", err) + c.Error(400, err) + return err + } + + betaTreasureIndexes := oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) + + // Generates ETH address. + betaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() + + u := models.UploadSession{ + Type: models.SessionTypeBeta, + GenesisHash: req.GenesisHash, + NumChunks: req.NumChunks, + FileSizeBytes: req.FileSizeBytes, + StorageLengthInYears: req.StorageLengthInYears, + TotalCost: req.Invoice.Cost, + ETHAddrAlpha: req.Invoice.EthAddress, + ETHAddrBeta: nulls.NewString(betaEthAddr.Hex()), + ETHPrivateKey: privKey, + Version: req.Version, + } + + defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_beta_session", analytics.NewProperties(). + Set("id", u.ID). + Set("genesis_hash", u.GenesisHash). + Set("file_size_byes", u.FileSizeBytes). + Set("num_chunks", u.NumChunks). + Set("storage_years", u.StorageLengthInYears)) + + if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { + dbID := []string{oyster_utils.InProgressDir, u.GenesisHash, oyster_utils.HashDir} + + db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) + if db == nil { + err := errors.New("error creating unique badger DB for hashes") + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + } + + vErr, err := u.StartUploadSession() + + if err != nil || vErr.HasAny() { + err = fmt.Errorf("Can't startUploadSession with validation error: %v and err: %v", vErr, err) + c.Error(400, err) + return err + } + + if len(vErr.Errors) > 0 { + c.Render(422, r.JSON(vErr.Errors)) + return err + } + + mergedIndexes, err := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, + oyster_utils.FileSectorInChunkSize, req.NumChunks) + + if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { + err := errors.New("no indexes selected for treasure") + fmt.Println(err) + c.Error(400, err) + return err + } + + if err != nil { + fmt.Println(err) + c.Error(400, err) + return err + } + for { + privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) + if err != nil { + err := errors.New("Could not generate eth keys: " + err.Error()) + fmt.Println(err) + c.Error(400, err) + return err + } + if len(mergedIndexes) != len(privateKeys) { + err := errors.New("privateKeys and mergedIndexes should have the same length") + fmt.Println(err) + c.Error(400, err) + return err + } + u.MakeTreasureIdxMap(mergedIndexes, privateKeys) + + treasureIndexes, err := u.GetTreasureIndexes() + + if u.TreasureStatus == models.TreasureInDataMapPending && + u.TreasureIdxMap.Valid && u.TreasureIdxMap.String != "" && + len(treasureIndexes) == len(mergedIndexes) { + models.DB.ValidateAndUpdate(&u) + break + } + } + + models.NewBrokerBrokerTransaction(&u) + + res := uploadSessionCreateBetaResV3{ + UploadSession: u, + ID: u.ID.String(), + Invoice: u.GetInvoice(), + BetaTreasureIndexes: betaTreasureIndexes, + } + //go waitForTransferAndNotifyBeta( + // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) + + return c.Render(200, r.JSON(res)) +} + +func (usr *UploadSessionResourceV3) GetPaymentStatus(c buffalo.Context) error { + start := PrometheusWrapper.TimeNow() + defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceGetPaymentStatus, start) + + session := models.UploadSession{} + err := models.DB.Find(&session, c.Param("id")) + + if err != nil { + c.Error(400, err) + oyster_utils.LogIfError(err, nil) + return err + } + if (session == models.UploadSession{}) { + err := errors.New("Did not find session that matched id" + c.Param("id")) + oyster_utils.LogIfError(err, nil) + c.Error(400, err) + return err + } + + // Force to check the status + if session.PaymentStatus != models.PaymentStatusConfirmed { + balance := EthWrapper.CheckPRLBalance(services.StringToAddress(session.ETHAddrAlpha.String)) + if balance.Int64() > 0 { + previousPaymentStatus := session.PaymentStatus + session.PaymentStatus = models.PaymentStatusConfirmed + err = models.DB.Save(&session) + if err != nil { + session.PaymentStatus = previousPaymentStatus + } else { + models.SetBrokerTransactionToPaid(session) + } + } + } + + res := paymentStatusCreateResV3{ + ID: session.ID.String(), + PaymentStatus: session.GetPaymentStatus(), + } + + return c.Render(200, r.JSON(res)) +} From 2c0d3e5eecb2518027d5e67ed8c255005b28a73f Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Thu, 4 Oct 2018 11:53:56 -0400 Subject: [PATCH 08/13] extract reused test helpers into separate file --- actions/test_helpers.go | 27 +++++++++++++++++++++++++++ actions/upload_sessions_test.go | 22 ++-------------------- 2 files changed, 29 insertions(+), 20 deletions(-) create mode 100644 actions/test_helpers.go diff --git a/actions/test_helpers.go b/actions/test_helpers.go new file mode 100644 index 00000000..c263f5a7 --- /dev/null +++ b/actions/test_helpers.go @@ -0,0 +1,27 @@ +package actions + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/oysterprotocol/brokernode/services" +) + +type mockWaitForTransfer struct { + hasCalled bool + input_brokerAddr common.Address + output_int *big.Int + output_error error +} + +type mockSendPrl struct { + hasCalled bool + input_msg services.OysterCallMsg + output_bool bool +} + +type mockCheckPRLBalance struct { + hasCalled bool + input_addr common.Address + output_int *big.Int +} diff --git a/actions/upload_sessions_test.go b/actions/upload_sessions_test.go index 98f58f50..03e4af42 100644 --- a/actions/upload_sessions_test.go +++ b/actions/upload_sessions_test.go @@ -3,36 +3,18 @@ package actions import ( "encoding/json" "fmt" - "github.com/oysterprotocol/brokernode/utils" "io/ioutil" "math/big" "time" + oyster_utils "github.com/oysterprotocol/brokernode/utils" + "github.com/ethereum/go-ethereum/common" "github.com/gobuffalo/pop/nulls" "github.com/oysterprotocol/brokernode/models" "github.com/oysterprotocol/brokernode/services" ) -type mockWaitForTransfer struct { - hasCalled bool - input_brokerAddr common.Address - output_int *big.Int - output_error error -} - -type mockSendPrl struct { - hasCalled bool - input_msg services.OysterCallMsg - output_bool bool -} - -type mockCheckPRLBalance struct { - hasCalled bool - input_addr common.Address - output_int *big.Int -} - func (suite *ActionSuite) Test_UploadSessionsCreate() { mockWaitForTransfer := mockWaitForTransfer{ output_error: nil, From fa167b7c15fa4eea5e7a4deffbfd22a979b3674a Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Thu, 4 Oct 2018 12:11:10 -0400 Subject: [PATCH 09/13] more helpers --- actions/test_helpers.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/actions/test_helpers.go b/actions/test_helpers.go index c263f5a7..bd222aea 100644 --- a/actions/test_helpers.go +++ b/actions/test_helpers.go @@ -25,3 +25,21 @@ type mockCheckPRLBalance struct { input_addr common.Address output_int *big.Int } + +func (v *mockWaitForTransfer) waitForTransfer(brokerAddr common.Address, transferType string) (*big.Int, error) { + v.hasCalled = true + v.input_brokerAddr = brokerAddr + return v.output_int, v.output_error +} + +func (v *mockSendPrl) sendPrl(msg services.OysterCallMsg) bool { + v.hasCalled = true + v.input_msg = msg + return v.output_bool +} + +func (v *mockCheckPRLBalance) checkPRLBalance(addr common.Address) *big.Int { + v.hasCalled = true + v.input_addr = addr + return v.output_int +} From bcf61496b65b6080f640bbdc37cce53db531db45 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Thu, 4 Oct 2018 12:11:24 -0400 Subject: [PATCH 10/13] remove from existing test --- actions/upload_sessions_test.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/actions/upload_sessions_test.go b/actions/upload_sessions_test.go index 03e4af42..4210dbf2 100644 --- a/actions/upload_sessions_test.go +++ b/actions/upload_sessions_test.go @@ -9,7 +9,6 @@ import ( oyster_utils "github.com/oysterprotocol/brokernode/utils" - "github.com/ethereum/go-ethereum/common" "github.com/gobuffalo/pop/nulls" "github.com/oysterprotocol/brokernode/models" "github.com/oysterprotocol/brokernode/services" @@ -293,21 +292,3 @@ func verifyPaymentConfirmation(sessionId string, suite *ActionSuite) { suite.Nil(err) suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) } - -func (v *mockWaitForTransfer) waitForTransfer(brokerAddr common.Address, transferType string) (*big.Int, error) { - v.hasCalled = true - v.input_brokerAddr = brokerAddr - return v.output_int, v.output_error -} - -func (v *mockSendPrl) sendPrl(msg services.OysterCallMsg) bool { - v.hasCalled = true - v.input_msg = msg - return v.output_bool -} - -func (v *mockCheckPRLBalance) checkPRLBalance(addr common.Address) *big.Int { - v.hasCalled = true - v.input_addr = addr - return v.output_int -} From 512942c6b584aecbb4155e042bb7bc648b77783d Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Thu, 4 Oct 2018 12:12:02 -0400 Subject: [PATCH 11/13] tests for v3 upload --- actions/upload_sessions_v3_test.go | 294 +++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 actions/upload_sessions_v3_test.go diff --git a/actions/upload_sessions_v3_test.go b/actions/upload_sessions_v3_test.go new file mode 100644 index 00000000..6869d7e8 --- /dev/null +++ b/actions/upload_sessions_v3_test.go @@ -0,0 +1,294 @@ +package actions + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "time" + + oyster_utils "github.com/oysterprotocol/brokernode/utils" + + "github.com/gobuffalo/pop/nulls" + "github.com/oysterprotocol/brokernode/models" + "github.com/oysterprotocol/brokernode/services" +) + +func (suite *ActionSuite) Test_UploadSessionsV3Create() { + mockWaitForTransfer := mockWaitForTransfer{ + output_error: nil, + output_int: big.NewInt(100), + } + mockSendPrl := mockSendPrl{ + output_bool: true, + } + EthWrapper = services.Eth{ + WaitForTransfer: mockWaitForTransfer.waitForTransfer, + SendPRL: mockSendPrl.sendPrl, + GenerateEthAddr: services.EthWrapper.GenerateEthAddr, + GenerateKeys: services.EthWrapper.GenerateKeys, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + res := suite.JSON("/api/v3/upload-sessions").Post(map[string]interface{}{ + "genesisHash": genHash, + "fileSizeBytes": 123, + "numChunks": 2, + "storageLengthInYears": 1, + }) + + // Parse response + resParsed := uploadSessionCreateRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(200, res.Code) + suite.Equal(genHash, resParsed.UploadSession.GenesisHash) + suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) + suite.Equal(models.SessionTypeAlpha, resParsed.UploadSession.Type) + suite.NotEqual(0, resParsed.Invoice.Cost) + suite.NotEqual("", resParsed.Invoice.EthAddress) + + time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + //suite.True(mockWaitForTransfer.hasCalled) + //suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) + + // mockCheckPRLBalance will result a positive value, and Alpha knows that beta has such balance, it won't send + // it again. + suite.False(mockSendPrl.hasCalled) + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + // verifyPaymentConfirmation(as, resParsed.ID) + + chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) + + suite.Equal(genHash, chunkData.Hash) + + brokerTx := []models.BrokerBrokerTransaction{} + + suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) + + suite.Equal(1, len(brokerTx)) +} + +func (suite *ActionSuite) Test_UploadSessionsV3CreateBeta() { + mockWaitForTransfer := mockWaitForTransfer{ + output_error: nil, + output_int: big.NewInt(100), + } + mockSendPrl := mockSendPrl{} + + EthWrapper = services.Eth{ + WaitForTransfer: mockWaitForTransfer.waitForTransfer, + SendPRL: mockSendPrl.sendPrl, + GenerateEthAddr: services.EthWrapper.GenerateEthAddr, + GenerateKeys: services.EthWrapper.GenerateKeys, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + res := suite.JSON("/api/v3/upload-sessions/beta").Post(map[string]interface{}{ + "genesisHash": genHash, + "fileSizeBytes": 123, + "numChunks": 2, + "storageLengthInYears": 1, + "alphaTreasureIndexes": []int{1}, + }) + + // Parse response + resParsed := uploadSessionCreateBetaRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + err = json.Unmarshal(bodyBytes, &resParsed) + suite.Nil(err) + + suite.Equal(200, res.Code) + suite.Equal(genHash, resParsed.UploadSession.GenesisHash) + suite.Equal(uint64(123), resParsed.UploadSession.FileSizeBytes) + suite.Equal(models.SessionTypeBeta, resParsed.UploadSession.Type) + suite.Equal(1, len(resParsed.BetaTreasureIndexes)) + suite.NotEqual(0, resParsed.Invoice.Cost) + suite.NotEqual("", resParsed.Invoice.EthAddress) + + time.Sleep(50 * time.Millisecond) // Force it to wait for goroutine to excute. + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out this test. + //suite.True(mockWaitForTransfer.hasCalled) + suite.Equal(services.StringToAddress(resParsed.UploadSession.ETHAddrAlpha.String), mockWaitForTransfer.input_brokerAddr) + suite.False(mockSendPrl.hasCalled) + + // TODO: fix waitForTransfer and uncomment it out in + // actions/upload_sessions.go then uncomment out these tests. + // verifyPaymentConfirmation(as, resParsed.ID) + + chunkData := models.GetSingleChunkData(oyster_utils.InProgressDir, genHash, int64(0)) + + suite.Equal(genHash, chunkData.Hash) + + brokerTx := []models.BrokerBrokerTransaction{} + + suite.DB.Where("genesis_hash = ?", genHash).All(&brokerTx) + + suite.Equal(1, len(brokerTx)) +} + +func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_Paid() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusConfirmed, + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + suite.False(mockCheckPRLBalance.hasCalled) +} + +func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_NoConfirmButCheckComplete() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(10), + } + mockSendPrl := mockSendPrl{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + SendPRL: mockSendPrl.sendPrl, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusPending, + ETHAddrAlpha: nulls.NewString("alpha"), + ETHAddrBeta: nulls.NewString("beta"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + + /* checkPRLBalance has been called once just for alpha. Sending + to beta now occurs in a job. */ + suite.True(mockCheckPRLBalance.hasCalled) + suite.False(mockSendPrl.hasCalled) + suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_NoConfirmAndCheckIncomplete() { + //setup + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(0), + } + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusInvoiced, + ETHAddrAlpha: nulls.NewString("alpha"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("invoiced", resParsed.PaymentStatus) + suite.True(mockCheckPRLBalance.hasCalled) + suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusInvoiced, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_BetaConfirmed() { + mockCheckPRLBalance := mockCheckPRLBalance{ + output_int: big.NewInt(10), + } + mockSendPrl := mockSendPrl{} + EthWrapper = services.Eth{ + CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, + SendPRL: mockSendPrl.sendPrl, + } + + genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) + + uploadSession1 := models.UploadSession{ + Type: models.SessionTypeBeta, + GenesisHash: genHash, + FileSizeBytes: 123, + NumChunks: 2, + PaymentStatus: models.PaymentStatusInvoiced, + ETHAddrAlpha: nulls.NewString("alpha"), + } + + resParsed := getPaymentStatus(uploadSession1, suite) + + suite.Equal("confirmed", resParsed.PaymentStatus) + suite.True(mockCheckPRLBalance.hasCalled) + suite.False(mockSendPrl.hasCalled) + + session := models.UploadSession{} + suite.Nil(suite.DB.Find(&session, resParsed.ID)) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} + +func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_DoesntExist() { + //res := suite.JSON("/api/v3/upload-sessions/" + "noIDFound").Get() + + //TODO: Return better error response when ID does not exist +} + +func getPaymentStatusV3(seededUploadSession models.UploadSession, suite *ActionSuite) paymentStatusCreateRes { + seededUploadSession.StartUploadSession() + + session := models.UploadSession{} + suite.Nil(suite.DB.Where("genesis_hash = ?", seededUploadSession.GenesisHash).First(&session)) + + //execute method + res := suite.JSON("/api/v3/upload-sessions/" + fmt.Sprint(session.ID)).Get() + + // Parse response + resParsed := paymentStatusCreateRes{} + bodyBytes, err := ioutil.ReadAll(res.Body) + suite.Nil(err) + + suite.Nil(json.Unmarshal(bodyBytes, &resParsed)) + + return resParsed +} + +func verifyPaymentConfirmationV3(sessionId string, suite *ActionSuite) { + session := models.UploadSession{} + err := suite.DB.Find(&session, sessionId) + suite.Nil(err) + suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) +} From 0498a7ba180443cf3b04fb52edbe666c71ee8c08 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Thu, 4 Oct 2018 12:12:13 -0400 Subject: [PATCH 12/13] v3 path --- actions/app.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/actions/app.go b/actions/app.go index 450f1a8c..69f6d3c0 100644 --- a/actions/app.go +++ b/actions/app.go @@ -104,6 +104,14 @@ func App() *buffalo.App { // Status statusResource := StatusResource{} apiV2.GET("status", statusResource.CheckStatus) + + apiV3 := app.Group("/api/v3") + uploadSessionV3Resource := UploadSessionResourceV3{} + // apiV2.Resource("/upload-sessions", &UploadSessionResource{&buffalo.BaseResource{}}) + apiV3.POST("upload-sessions", uploadSessionV3Resource.Create) + apiV3.PUT("upload-sessions/{id}", uploadSessionV3Resource.Update) + apiV3.POST("upload-sessions/beta", uploadSessionV3Resource.CreateBeta) + apiV3.GET("upload-sessions/{id}", uploadSessionV3Resource.GetPaymentStatus) } oyster_utils.StartProfile() From a390e1242e55e0000f13a2e15bb0e071ac5c6a35 Mon Sep 17 00:00:00 2001 From: Edmund Mai Date: Fri, 5 Oct 2018 14:40:55 -0400 Subject: [PATCH 13/13] routes --- actions/app.go | 6 +- actions/upload_sessions_v3.go | 374 ----------------------------- actions/upload_sessions_v3_test.go | 133 ---------- 3 files changed, 3 insertions(+), 510 deletions(-) diff --git a/actions/app.go b/actions/app.go index 69f6d3c0..e901bfb1 100644 --- a/actions/app.go +++ b/actions/app.go @@ -108,10 +108,10 @@ func App() *buffalo.App { apiV3 := app.Group("/api/v3") uploadSessionV3Resource := UploadSessionResourceV3{} // apiV2.Resource("/upload-sessions", &UploadSessionResource{&buffalo.BaseResource{}}) - apiV3.POST("upload-sessions", uploadSessionV3Resource.Create) + apiV3.POST("upload-sessions", uploadSessionResource.Create) apiV3.PUT("upload-sessions/{id}", uploadSessionV3Resource.Update) - apiV3.POST("upload-sessions/beta", uploadSessionV3Resource.CreateBeta) - apiV3.GET("upload-sessions/{id}", uploadSessionV3Resource.GetPaymentStatus) + apiV3.POST("upload-sessions/beta", uploadSessionResource.CreateBeta) + apiV3.GET("upload-sessions/{id}", uploadSessionResource.GetPaymentStatus) } oyster_utils.StartProfile() diff --git a/actions/upload_sessions_v3.go b/actions/upload_sessions_v3.go index a8ec4a55..931cad20 100644 --- a/actions/upload_sessions_v3.go +++ b/actions/upload_sessions_v3.go @@ -1,18 +1,11 @@ package actions import ( - "bytes" - "encoding/json" "fmt" - "net/http" - "os" - "strconv" "time" "github.com/gobuffalo/buffalo" - "github.com/gobuffalo/pop/nulls" "github.com/oysterprotocol/brokernode/models" - "github.com/oysterprotocol/brokernode/services" oyster_utils "github.com/oysterprotocol/brokernode/utils" "github.com/pkg/errors" analytics "gopkg.in/segmentio/analytics-go.v3" @@ -24,222 +17,14 @@ type UploadSessionResourceV3 struct { // Request Response structs -type uploadSessionCreateReqV3 struct { - GenesisHash string `json:"genesisHash"` - NumChunks int `json:"numChunks"` - FileSizeBytes uint64 `json:"fileSizeBytes"` // This is Trytes instead of Byte - BetaIP string `json:"betaIp"` - StorageLengthInYears int `json:"storageLengthInYears"` - AlphaTreasureIndexes []int `json:"alphaTreasureIndexes"` - Invoice models.Invoice `json:"invoice"` - Version uint32 `json:"version"` -} - -type uploadSessionCreateResV3 struct { - ID string `json:"id"` - UploadSession models.UploadSession `json:"uploadSession"` - BetaSessionID string `json:"betaSessionId"` - Invoice models.Invoice `json:"invoice"` -} - -type uploadSessionCreateBetaResV3 struct { - ID string `json:"id"` - UploadSession models.UploadSession `json:"uploadSession"` - BetaSessionID string `json:"betaSessionId"` - Invoice models.Invoice `json:"invoice"` - BetaTreasureIndexes []int `json:"betaTreasureIndexes"` -} - type UploadSessionUpdateReqV3 struct { Chunks []models.ChunkReq `json:"chunks"` } -type paymentStatusCreateResV3 struct { - ID string `json:"id"` - PaymentStatus string `json:"paymentStatus"` -} - -var NumChunksLimitV3 = -1 //unlimited - func init() { } -// Create creates an upload session. -func (usr *UploadSessionResourceV3) Create(c buffalo.Context) error { - - if os.Getenv("DEPLOY_IN_PROGRESS") == "true" { - err := errors.New("Deployment in progress. Try again later") - fmt.Println(err) - c.Error(400, err) - return err - } - - if v, err := strconv.Atoi(os.Getenv("NUM_CHUNKS_LIMIT")); err == nil { - NumChunksLimitV3 = v - } - - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreate, start) - - req := uploadSessionCreateReqV3{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - if NumChunksLimitV3 != -1 && req.NumChunks > NumChunksLimitV3 { - err := errors.New("This broker has a limit of " + fmt.Sprint(NumChunksLimitV3) + " file chunks.") - fmt.Println(err) - c.Error(400, err) - return err - } - - alphaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() - - // Start Alpha Session. - alphaSession := models.UploadSession{ - Type: models.SessionTypeAlpha, - GenesisHash: req.GenesisHash, - FileSizeBytes: req.FileSizeBytes, - NumChunks: req.NumChunks, - StorageLengthInYears: req.StorageLengthInYears, - ETHAddrAlpha: nulls.NewString(alphaEthAddr.Hex()), - ETHPrivateKey: privKey, - Version: req.Version, - } - - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_alpha_session", analytics.NewProperties(). - Set("id", alphaSession.ID). - Set("genesis_hash", alphaSession.GenesisHash). - Set("file_size_byes", alphaSession.FileSizeBytes). - Set("num_chunks", alphaSession.NumChunks). - Set("storage_years", alphaSession.StorageLengthInYears)) - - if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { - - dbID := []string{oyster_utils.InProgressDir, alphaSession.GenesisHash, oyster_utils.HashDir} - - db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) - if db == nil { - err := errors.New("error creating unique badger DB for hashes") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - } - - vErr, err := alphaSession.StartUploadSession() - if err != nil || vErr.HasAny() { - err = fmt.Errorf("StartUploadSession error: %v and validation error: %v", err, vErr) - c.Error(400, err) - return err - } - - invoice := alphaSession.GetInvoice() - - // Mutates this because copying in golang sucks... - req.Invoice = invoice - - req.AlphaTreasureIndexes = oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) - - // Start Beta Session. - var betaSessionID = "" - var betaTreasureIndexes []int - hasBeta := req.BetaIP != "" - if hasBeta { - betaReq, err := json.Marshal(req) - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - reqBetaBody := bytes.NewBuffer(betaReq) - - // Should we be hardcoding the port? - betaURL := req.BetaIP + ":3000/api/v2/upload-sessions/beta" - betaRes, err := http.Post(betaURL, "application/json", reqBetaBody) - defer betaRes.Body.Close() // we need to close the connection - if err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - betaSessionRes := &uploadSessionCreateBetaResV3{} - if err := oyster_utils.ParseResBody(betaRes, betaSessionRes); err != nil { - err = fmt.Errorf("Unable to communicate with Beta node: %v", err) - // This should consider as BadRequest since the client pick the beta node. - c.Error(400, err) - return err - } - - betaSessionID = betaSessionRes.ID - - betaTreasureIndexes = betaSessionRes.BetaTreasureIndexes - alphaSession.ETHAddrBeta = betaSessionRes.UploadSession.ETHAddrBeta - } - - if err := models.DB.Save(&alphaSession); err != nil { - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - models.NewBrokerBrokerTransaction(&alphaSession) - - if hasBeta { - mergedIndexes, _ := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, - oyster_utils.FileSectorInChunkSize, req.NumChunks) - - if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { - err := errors.New("no indexes selected for treasure") - fmt.Println(err) - c.Error(400, err) - return err - } - - for { - privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) - if err != nil { - err := errors.New("Could not generate eth keys: " + err.Error()) - fmt.Println(err) - c.Error(400, err) - return err - } - if len(mergedIndexes) != len(privateKeys) { - err := errors.New("privateKeys and mergedIndexes should have the same length") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - // Update alpha treasure idx map. - alphaSession.MakeTreasureIdxMap(mergedIndexes, privateKeys) - - treasureIndexes, _ := alphaSession.GetTreasureIndexes() - - if alphaSession.TreasureStatus == models.TreasureInDataMapPending && - alphaSession.TreasureIdxMap.Valid && alphaSession.TreasureIdxMap.String != "" && - len(treasureIndexes) == len(mergedIndexes) { - models.DB.ValidateAndUpdate(&alphaSession) - break - } - } - } - - res := uploadSessionCreateResV3{ - UploadSession: alphaSession, - ID: alphaSession.ID.String(), - BetaSessionID: betaSessionID, - Invoice: invoice, - } - //go waitForTransferAndNotifyBeta( - // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) - - return c.Render(200, r.JSON(res)) -} - // Update uploads a chunk associated with an upload session. func (usr *UploadSessionResourceV3) Update(c buffalo.Context) error { start := PrometheusWrapper.TimeNow() @@ -304,162 +89,3 @@ func (usr *UploadSessionResourceV3) Update(c buffalo.Context) error { return c.Render(202, r.JSON(map[string]bool{"success": true})) } - -// CreateBeta creates an upload session on the beta broker. -func (usr *UploadSessionResourceV3) CreateBeta(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceCreateBeta, start) - - req := uploadSessionCreateReqV3{} - if err := oyster_utils.ParseReqBody(c.Request(), &req); err != nil { - err = fmt.Errorf("Invalid request, unable to parse request body %v", err) - c.Error(400, err) - return err - } - - betaTreasureIndexes := oyster_utils.GenerateInsertedIndexesForPearl(oyster_utils.ConvertToByte(req.FileSizeBytes)) - - // Generates ETH address. - betaEthAddr, privKey, _ := EthWrapper.GenerateEthAddr() - - u := models.UploadSession{ - Type: models.SessionTypeBeta, - GenesisHash: req.GenesisHash, - NumChunks: req.NumChunks, - FileSizeBytes: req.FileSizeBytes, - StorageLengthInYears: req.StorageLengthInYears, - TotalCost: req.Invoice.Cost, - ETHAddrAlpha: req.Invoice.EthAddress, - ETHAddrBeta: nulls.NewString(betaEthAddr.Hex()), - ETHPrivateKey: privKey, - Version: req.Version, - } - - defer oyster_utils.TimeTrack(time.Now(), "actions/upload_sessions: create_beta_session", analytics.NewProperties(). - Set("id", u.ID). - Set("genesis_hash", u.GenesisHash). - Set("file_size_byes", u.FileSizeBytes). - Set("num_chunks", u.NumChunks). - Set("storage_years", u.StorageLengthInYears)) - - if oyster_utils.DataMapStorageMode == oyster_utils.DataMapsInBadger { - dbID := []string{oyster_utils.InProgressDir, u.GenesisHash, oyster_utils.HashDir} - - db := oyster_utils.GetOrInitUniqueBadgerDB(dbID) - if db == nil { - err := errors.New("error creating unique badger DB for hashes") - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - } - - vErr, err := u.StartUploadSession() - - if err != nil || vErr.HasAny() { - err = fmt.Errorf("Can't startUploadSession with validation error: %v and err: %v", vErr, err) - c.Error(400, err) - return err - } - - if len(vErr.Errors) > 0 { - c.Render(422, r.JSON(vErr.Errors)) - return err - } - - mergedIndexes, err := oyster_utils.MergeIndexes(req.AlphaTreasureIndexes, betaTreasureIndexes, - oyster_utils.FileSectorInChunkSize, req.NumChunks) - - if len(mergedIndexes) == 0 && oyster_utils.BrokerMode != oyster_utils.TestModeNoTreasure { - err := errors.New("no indexes selected for treasure") - fmt.Println(err) - c.Error(400, err) - return err - } - - if err != nil { - fmt.Println(err) - c.Error(400, err) - return err - } - for { - privateKeys, err := EthWrapper.GenerateKeys(len(mergedIndexes)) - if err != nil { - err := errors.New("Could not generate eth keys: " + err.Error()) - fmt.Println(err) - c.Error(400, err) - return err - } - if len(mergedIndexes) != len(privateKeys) { - err := errors.New("privateKeys and mergedIndexes should have the same length") - fmt.Println(err) - c.Error(400, err) - return err - } - u.MakeTreasureIdxMap(mergedIndexes, privateKeys) - - treasureIndexes, err := u.GetTreasureIndexes() - - if u.TreasureStatus == models.TreasureInDataMapPending && - u.TreasureIdxMap.Valid && u.TreasureIdxMap.String != "" && - len(treasureIndexes) == len(mergedIndexes) { - models.DB.ValidateAndUpdate(&u) - break - } - } - - models.NewBrokerBrokerTransaction(&u) - - res := uploadSessionCreateBetaResV3{ - UploadSession: u, - ID: u.ID.String(), - Invoice: u.GetInvoice(), - BetaTreasureIndexes: betaTreasureIndexes, - } - //go waitForTransferAndNotifyBeta( - // res.UploadSession.ETHAddrAlpha.String, res.UploadSession.ETHAddrBeta.String, res.ID) - - return c.Render(200, r.JSON(res)) -} - -func (usr *UploadSessionResourceV3) GetPaymentStatus(c buffalo.Context) error { - start := PrometheusWrapper.TimeNow() - defer PrometheusWrapper.HistogramSeconds(PrometheusWrapper.HistogramUploadSessionResourceGetPaymentStatus, start) - - session := models.UploadSession{} - err := models.DB.Find(&session, c.Param("id")) - - if err != nil { - c.Error(400, err) - oyster_utils.LogIfError(err, nil) - return err - } - if (session == models.UploadSession{}) { - err := errors.New("Did not find session that matched id" + c.Param("id")) - oyster_utils.LogIfError(err, nil) - c.Error(400, err) - return err - } - - // Force to check the status - if session.PaymentStatus != models.PaymentStatusConfirmed { - balance := EthWrapper.CheckPRLBalance(services.StringToAddress(session.ETHAddrAlpha.String)) - if balance.Int64() > 0 { - previousPaymentStatus := session.PaymentStatus - session.PaymentStatus = models.PaymentStatusConfirmed - err = models.DB.Save(&session) - if err != nil { - session.PaymentStatus = previousPaymentStatus - } else { - models.SetBrokerTransactionToPaid(session) - } - } - } - - res := paymentStatusCreateResV3{ - ID: session.ID.String(), - PaymentStatus: session.GetPaymentStatus(), - } - - return c.Render(200, r.JSON(res)) -} diff --git a/actions/upload_sessions_v3_test.go b/actions/upload_sessions_v3_test.go index 6869d7e8..111e3fe1 100644 --- a/actions/upload_sessions_v3_test.go +++ b/actions/upload_sessions_v3_test.go @@ -2,14 +2,12 @@ package actions import ( "encoding/json" - "fmt" "io/ioutil" "math/big" "time" oyster_utils "github.com/oysterprotocol/brokernode/utils" - "github.com/gobuffalo/pop/nulls" "github.com/oysterprotocol/brokernode/models" "github.com/oysterprotocol/brokernode/services" ) @@ -161,134 +159,3 @@ func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_Paid() { suite.Equal("confirmed", resParsed.PaymentStatus) suite.False(mockCheckPRLBalance.hasCalled) } - -func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_NoConfirmButCheckComplete() { - //setup - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(10), - } - mockSendPrl := mockSendPrl{} - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - SendPRL: mockSendPrl.sendPrl, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusPending, - ETHAddrAlpha: nulls.NewString("alpha"), - ETHAddrBeta: nulls.NewString("beta"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("confirmed", resParsed.PaymentStatus) - - /* checkPRLBalance has been called once just for alpha. Sending - to beta now occurs in a job. */ - suite.True(mockCheckPRLBalance.hasCalled) - suite.False(mockSendPrl.hasCalled) - suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_NoConfirmAndCheckIncomplete() { - //setup - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(0), - } - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusInvoiced, - ETHAddrAlpha: nulls.NewString("alpha"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("invoiced", resParsed.PaymentStatus) - suite.True(mockCheckPRLBalance.hasCalled) - suite.Equal(services.StringToAddress(uploadSession1.ETHAddrAlpha.String), mockCheckPRLBalance.input_addr) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusInvoiced, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_BetaConfirmed() { - mockCheckPRLBalance := mockCheckPRLBalance{ - output_int: big.NewInt(10), - } - mockSendPrl := mockSendPrl{} - EthWrapper = services.Eth{ - CheckPRLBalance: mockCheckPRLBalance.checkPRLBalance, - SendPRL: mockSendPrl.sendPrl, - } - - genHash := oyster_utils.RandSeq(8, []rune("abcdef0123456789")) - - uploadSession1 := models.UploadSession{ - Type: models.SessionTypeBeta, - GenesisHash: genHash, - FileSizeBytes: 123, - NumChunks: 2, - PaymentStatus: models.PaymentStatusInvoiced, - ETHAddrAlpha: nulls.NewString("alpha"), - } - - resParsed := getPaymentStatus(uploadSession1, suite) - - suite.Equal("confirmed", resParsed.PaymentStatus) - suite.True(mockCheckPRLBalance.hasCalled) - suite.False(mockSendPrl.hasCalled) - - session := models.UploadSession{} - suite.Nil(suite.DB.Find(&session, resParsed.ID)) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -} - -func (suite *ActionSuite) Test_UploadSessionsV3GetPaymentStatus_DoesntExist() { - //res := suite.JSON("/api/v3/upload-sessions/" + "noIDFound").Get() - - //TODO: Return better error response when ID does not exist -} - -func getPaymentStatusV3(seededUploadSession models.UploadSession, suite *ActionSuite) paymentStatusCreateRes { - seededUploadSession.StartUploadSession() - - session := models.UploadSession{} - suite.Nil(suite.DB.Where("genesis_hash = ?", seededUploadSession.GenesisHash).First(&session)) - - //execute method - res := suite.JSON("/api/v3/upload-sessions/" + fmt.Sprint(session.ID)).Get() - - // Parse response - resParsed := paymentStatusCreateRes{} - bodyBytes, err := ioutil.ReadAll(res.Body) - suite.Nil(err) - - suite.Nil(json.Unmarshal(bodyBytes, &resParsed)) - - return resParsed -} - -func verifyPaymentConfirmationV3(sessionId string, suite *ActionSuite) { - session := models.UploadSession{} - err := suite.DB.Find(&session, sessionId) - suite.Nil(err) - suite.Equal(models.PaymentStatusConfirmed, session.PaymentStatus) -}