diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index da569c0c..a2467ae8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,5 +2,5 @@ - [ ] The code follows our code [conventions](https://github.com/my-cloud/ruthenium/blob/main/.github/CONTRIBUTING.md#go). - [ ] Important principle changes have been documented in the [wiki](https://github.com/my-cloud/ruthenium/wiki). - [ ] The new code is covered by unit tests with assertions. -- [ ] The behaviour [node](https://github.com/my-cloud/ruthenium/blob/main/src/node) and [UI](https://github.com/my-cloud/ruthenium/blob/main/src/ui) have been manually tested and is as expected. +- [ ] The behaviour [validator node](https://github.com/my-cloud/ruthenium/blob/main/validatornode) and [access node](https://github.com/my-cloud/ruthenium/blob/main/accessnode) have been manually tested and is as expected. - [ ] The squash commit message follows our [conventions](https://github.com/my-cloud/ruthenium/blob/main/.github/CONTRIBUTING.md#git). diff --git a/.gitignore b/.gitignore index 33edee0d..6244a4e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ ##IDEs -#JetBrain Goland +#JetBrains .idea/** #Microsoft VSCode .vscode/** -#Microsoft VS -.vs/** ##Build *.exe + +##Settings +*/settings?.json diff --git a/Dockerfile b/Dockerfile index 1ce45642..832ed795 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,15 @@ -FROM golang:1.19 as builder +FROM golang:1.19 AS builder WORKDIR /app -COPY ./src ./src -COPY ./config ./config +COPY ./validatornode ./validatornode +COPY ./accessnode ./accessnode ADD go.mod . ADD go.sum . -RUN CGO_ENABLED=0 go build -o node src/node/main.go -RUN CGO_ENABLED=0 go build -o ui src/ui/main.go +RUN CGO_ENABLED=0 go build -o validatornode validatornode/main.go +RUN CGO_ENABLED=0 go build -o accessnode accessnode/main.go -FROM gcr.io/distroless/static-debian11 +FROM debian:11.9 +USER nonroot WORKDIR /app -COPY ./templates /app/templates -COPY --from=builder /app/config /app/config -COPY --from=builder /app/node /app -COPY --from=builder /app/ui /app +COPY --from=builder /app/validatornode /app +COPY --from=builder /app/accessnode /app diff --git a/README.md b/README.md index f1b98c39..b42a8c07 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ There are two ways to run a Ruthenium node. You can either use your own build fr * If you are using Windows, you need to have [tdm-gcc](https://jmeubank.github.io/tdm-gcc/) installed. * Option B (using docker image): * You need to have [![Docker](https://img.shields.io/badge/docker-grey?logo=docker)](https://www.docker.com/) installed. -* Your firewall port 10600 must be open (please read "Program arguments" section of the [node](src/node/README.md#program-arguments) and [UI server](src/ui/README.md#program-arguments) documentation if you want to use another port than 10600). +* Your firewall port 10600 must be open (please read "Program arguments" section of the [validator node](validatornode/README.md#program-arguments) and [access node](accessnode/README.md#program-arguments) documentation if you want to use another port than 10600). * To get an income or validate blocks ou need to be registered in the [Proof of Humanity](https://github.com/my-cloud/ruthenium/wiki/Whitepaper#proof-of-humanity) registry. ### Installation @@ -39,29 +39,29 @@ There are two ways to run a Ruthenium node. You can either use your own build fr ### Launch * Option A (using sources): * Extract files from the sources archive - * At root level (ruthenium folder), run the [node](src/node/README.md): + * At root level (ruthenium folder), run the [validator node](validatornode/README.md): ``` - go run src/node/main.go -private-key= + go run validatornode/main.go ``` - * At root level (ruthenium folder), run the [UI server](src/ui/README.md): + * At root level (ruthenium folder), run the [access node](accessnode/README.md): ``` - go run src/ui/main.go -host-ip= + go run accessnode/main.go ``` * Option B (using docker image): - * Run the [node](src/node/README.md): + * Run the [validator node](validatornode/README.md): ``` - sudo docker run -p 10600:10600 -ti ghcr.io/my-cloud/ruthenium:latest \app\node -private-key= + sudo docker run -p 10600:10600 -ti ghcr.io/my-cloud/ruthenium:latest \app\validatornode ``` - * Run the [UI server](src/ui/README.md): + * Run the [access node](accessnode/README.md): ``` - sudo docker run -p 8080:8080 -ti ghcr.io/my-cloud/ruthenium:latest \app\ui -host-ip= + sudo docker run -p 8080:8080 -ti ghcr.io/my-cloud/ruthenium:latest \app\accessnode ``` * Using a web browser, go to: * http://localhost:8080 ## APIs -* [host node API](src/node/README.md#api) -* [UI server API](src/ui/README.md#api) +* [validator node API](validatornode/README.md#api) +* [access node API](accessnode/README.md#api) ## Contributing [![Forks](https://img.shields.io/github/forks/my-cloud/ruthenium?logo=github)](https://github.com/my-cloud/ruthenium/fork) diff --git a/accessnode/README.md b/accessnode/README.md new file mode 100644 index 00000000..83d5a861 --- /dev/null +++ b/accessnode/README.md @@ -0,0 +1,477 @@ +# Access Node +The access node interacts with the [validator node](../validatornode/README.md) through a P2P connection, and exposes a HTTP REST [API](#api) +Any other implementation of this access node can communicate with a validator node using its [API](../validatornode/README.md#api). +In this repository, a simple user interface is defined in a `template.html`. + +## Prerequisites +A Ruthenium node must be running. + +## Launch +At root level (ruthenium folder), run the access node using the command `go run accessnode/main.go` with the add of some [program arguments](#program-arguments). For example: +``` +go run accessnode/main.go -validator-ip=0.0.0.0 +``` + +## Program Arguments +``` +-port: The TCP port number for the access node (default: "8080") +-validator-ip: The validator node IP or DNS address (default: "127.0.0.1") +-validator-port: The TCP port number of the validator node (accepted values: "10600" for mainnet, "10601" to "10699" for testnet, default: "10600") +-template-path: The UI template path (default: "accessnode/presentation/api/template.html") +-log-level: The log level (accepted values: "debug", "info", "warn", "error", "fatal", default: "info") +-settings-path: The settings file path (default: "accessnode/settings.json") +``` + +Using a web browser, go to `http://localhost:8080` (Depending on settings, replace `localhost` by the UI server IP address and `8080` by the TCP port number for the UI server) + +## Application settings + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "host": { + "port": int + }, + "template": { + "path": string + }, + "validator": { + "ip": string + "port": int + }, + "log": { + "level": string + } +} +``` + + +``` + + +The access node TCP port number + + +The User Interface html template path + + +The validator node IP or DNS address +The validator node TCP port number (accepted values: "10600" for mainnet, "10601" to "10699" for testnet) + + +The log level (accepted values: "debug", "info", "warn", "error", "fatal") + + +``` + + +``` +{ + "host": { + "port": 8080 + }, + "template": { + "path": "accessnode/presentation/api/template.html" + }, + "validator": { + "ip": "127.0.0.1", + "port": 10600 + }, + "log": { + "level": "info" + } +} +``` +
+ +## API +Base URL: `:` (example: `localhost:8080`) + +### Payment +
+Add transaction + +![POST](https://img.shields.io/badge/POST-seagreen?style=flat-square) +![/transaction](https://img.shields.io/badge//transaction-dimgray?style=flat-square) + +*Description:* Add a transaction to the transactions pool. +* **parameters:** *none* +* **request body:** [TransactionRequest](#transactionrequest) +* **responses:** + + | Code | Description | + |------|------------------------------------------------------------| + | 201 | Transaction added | + | 400 | Bad request, if any request argument is invalid | + | 500 | Internal server error, if an unexpected condition occurred | +
+
+Get transaction info + +![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) +![/transaction/info](https://img.shields.io/badge//transaction/info-dimgray?style=flat-square) + +*Description:* Get the transaction data needed for a transaction request. +* **parameters:** + + | Name | Description | Example | + |-----------|--------------------------------------------------------|----------------------------------------------| + | `address` | 42 characters hexadecimal sender wallet address | `0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a` | + | `value` | 64 bits floating-point number value of the transaction | `0` | +* **request body:** *none* +* **responses:** + + | Code | Description | + |------|----------------------------------------------------------------------------------| + | 200 | [TransactionInfo](#transactioninfo) | + | 400 | Bad request, if any request argument is invalid | + | 405 | Method not allowed, if the value exceeds the wallet amount for the given address | + | 500 | Internal server error, if an unexpected condition occurred | +
+
+Get transactions + +![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) +![/transactions](https://img.shields.io/badge//transactions-dimgray?style=flat-square) + +*Description:* Get all the transactions of the current transactions pool. +* **parameters:** *none* +* **request body:** *none* +* **responses:** + + | Code | Description | + |------|------------------------------------------------------------| + | 200 | Array of [transactions](#transaction) | + | 500 | Internal server error, if an unexpected condition occurred | +
+ +### Wallet +
+Get wallet address + +![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) +![/wallet/address](https://img.shields.io/badge//wallet/address-dimgray?style=flat-square) + +*Description:* Get the wallet address depending on the given public key. +* **parameters:** *none* + + | Name | Description | Example | + |-------------|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| + | `publicKey` | 132 characters hexadecimal public key | `0x046bd857ce80ff5238d6561f3a775802453c570b6ea2cbf93a35a8a6542b2edbe5f625f9e3fbd2a5df62adebc27391332a265fb94340fb11b69cf569605a5df782` | +* **request body:** *none* +* **responses:** + + | Code | Description | + |------|------------------------------------------------------------| + | 200 | 42 characters hexadecimal wallet address | + | 500 | Internal server error, if an unexpected condition occurred | +
+
+Get wallet amount + +![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) +![/wallet/amount](https://img.shields.io/badge//wallet/amount-dimgray?style=flat-square) + +*Description:* Get the amount for the given wallet address. +* **parameters:** + + | Name | Description | Example | + |-----------|------------------------------------------|----------------------------------------------| + | `address` | 42 characters hexadecimal wallet address | `0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a` | +* **request body:** *none* +* **responses:** + + | Code | Description | + |------|------------------------------------------------------------| + | 200 | 64 bits floating-point number amount | + | 400 | Bad request, if any request argument is invalid | + | 500 | Internal server error, if an unexpected condition occurred | +
+ +--- + +### Schemas + +#### Input + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "output_index": uint16 + "transaction_id": string + "public_key": string + "signature": string +} +``` + + +``` + +The output index +The ID of the transaction holding the output +The output recipient public key +The output signature + +``` + + +``` +{ + "output_index": 0 + "transaction_id": "8ae72a72c0c99dc9d41c2b7d8ea67b5a2de25ff4463b1a53816ba179947ce77d" + "public_key": "0x046bd857ce80ff5238d6561f3a775802453c570b6ea2cbf93a35a8a6542b2edbe5f625f9e3fbd2a5df62adebc27391332a265fb94340fb11b69cf569605a5df782" + "signature": "4f3b24cbb4d2c13aaf60518fce70409fd29e1668db1c2109c0eac58427c203df59788bade6d5f3eb9df161b4ed3de451bac64f4c54e74578d69caf8cd401a38f" +} +``` +
+ +#### InputInfo + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "output_index": uint16 + "transaction_id": string +} +``` + + +``` + +The output index +The ID of the transaction holding the output + +``` + + +``` +{ + "output_index": 0 + "transaction_id": "8ae72a72c0c99dc9d41c2b7d8ea67b5a2de25ff4463b1a53816ba179947ce77d" +} +``` +
+ +#### Output + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "address": string + "is_yielding": bool + "value": uint64 +} +``` + + +``` + +The address of this output recipient +Whether this output should be used for income calculation +The value at the transaction timestamp + +``` + + +``` +{ + "address": "0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a" + "is_yielding": true + "value": 0 +} +``` +
+ +#### Transaction + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "id": string + "inputs": []Input + "outputs": []Output + "timestamp": int64 +} +``` + + +``` + +The ID +The inputs +The outputs +The timestamp + +``` + + +``` +{ + "id": "30148389df42b7cd0cb0d3ce951133da3f36ff4e1581d108da1ee05bacad64b7" + "inputs": [] + "outputs": [] + "timestamp": 1667768884780639700 +} +``` +
+ +#### TransactionInfo + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "inputs": []InputInfo + "rest": uint64 + "timestamp": int64 +} +``` + + +``` + +The remaining amount to be used as a value for the output with the sender address +The utxos to be used as inputs of the transaction + +``` + + +``` +{ + "inputs": [] + "rest": 0 + "timestamp": 1667768884780639700 +} +``` +
+ +#### TransactionRequest + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "transaction": Transaction + "transaction_broadcaster_target": string +} +``` + + +``` + +The transaction +The transaction broadcaster target + +``` + + +``` +{ + "transaction": {} + "transaction_broadcaster_target": "0.0.0.0:0000" +} +``` +
diff --git a/accessnode/infrastructure/configuration/host_settings.go b/accessnode/infrastructure/configuration/host_settings.go new file mode 100644 index 00000000..ffcec854 --- /dev/null +++ b/accessnode/infrastructure/configuration/host_settings.go @@ -0,0 +1,28 @@ +package configuration + +import ( + "encoding/json" + "strconv" +) + +type hostSettingsDto struct { + Port int +} + +type HostSettings struct { + port string +} + +func (settings *HostSettings) UnmarshalJSON(data []byte) error { + var dto *hostSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.port = strconv.Itoa(dto.Port) + return nil +} + +func (settings *HostSettings) Port() string { + return settings.port +} diff --git a/accessnode/infrastructure/configuration/settings.go b/accessnode/infrastructure/configuration/settings.go new file mode 100644 index 00000000..102f1101 --- /dev/null +++ b/accessnode/infrastructure/configuration/settings.go @@ -0,0 +1,71 @@ +package configuration + +import ( + "encoding/json" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/configuration" + "io" + "os" +) + +type settingsDto struct { + Host *HostSettings + Template *TemplateSettings + Validator *configuration.HostSettings + Log *configuration.LogSettings +} + +type Settings struct { + host *HostSettings + template *TemplateSettings + validator *configuration.HostSettings + log *configuration.LogSettings +} + +func NewSettings(path string) (*Settings, error) { + jsonFile, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open file: %w", err) + } + var settings *Settings + bytes, err := io.ReadAll(jsonFile) + if err != nil { + return nil, fmt.Errorf("unable to read file: %w", err) + } + if err = jsonFile.Close(); err != nil { + return nil, fmt.Errorf("unable to close file: %w", err) + } + if err = json.Unmarshal(bytes, &settings); err != nil { + return nil, fmt.Errorf("unable to unmarshal: %w", err) + } + return settings, nil +} + +func (settings *Settings) UnmarshalJSON(data []byte) error { + var dto *settingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.host = dto.Host + settings.validator = dto.Validator + settings.template = dto.Template + settings.log = dto.Log + return nil +} + +func (settings *Settings) Host() *HostSettings { + return settings.host +} + +func (settings *Settings) Template() *TemplateSettings { + return settings.template +} + +func (settings *Settings) Validator() *configuration.HostSettings { + return settings.validator +} + +func (settings *Settings) Log() *configuration.LogSettings { + return settings.log +} diff --git a/accessnode/infrastructure/configuration/template_settings.go b/accessnode/infrastructure/configuration/template_settings.go new file mode 100644 index 00000000..c54f66e0 --- /dev/null +++ b/accessnode/infrastructure/configuration/template_settings.go @@ -0,0 +1,27 @@ +package configuration + +import ( + "encoding/json" +) + +type templateSettingsDto struct { + Path string +} + +type TemplateSettings struct { + path string +} + +func (settings *TemplateSettings) UnmarshalJSON(data []byte) error { + var dto *templateSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.path = dto.Path + return nil +} + +func (settings *TemplateSettings) Path() string { + return settings.path +} diff --git a/accessnode/infrastructure/io/response.go b/accessnode/infrastructure/io/response.go new file mode 100644 index 00000000..03e4434c --- /dev/null +++ b/accessnode/infrastructure/io/response.go @@ -0,0 +1,40 @@ +package io + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type Response struct { + writer http.ResponseWriter + logger log.Logger +} + +func NewResponse(writer http.ResponseWriter, logger log.Logger) *Response { + return &Response{writer, logger} +} + +func (response *Response) Write(statusCode int, message string) { + response.writer.WriteHeader(statusCode) + i, err := io.WriteString(response.writer, message) + if err != nil || i == 0 { + response.logger.Error(fmt.Sprintf("failed to write message: %s", message)) + } +} + +func (response *Response) WriteJson(statusCode int, object interface{}) { + marshaledObject, err := json.Marshal(object) + if err != nil { + errorMessage := "failed to marshal response message" + response.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + response.writer.Header().Add("Content-Type", "application/json") + message := string(marshaledObject[:]) + response.Write(statusCode, message) +} diff --git a/accessnode/main.go b/accessnode/main.go new file mode 100644 index 00000000..654740da --- /dev/null +++ b/accessnode/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "github.com/my-cloud/ruthenium/accessnode/infrastructure/configuration" + "time" + + "github.com/my-cloud/ruthenium/accessnode/presentation" + "github.com/my-cloud/ruthenium/validatornode/domain/clock" + validatorconfiguration "github.com/my-cloud/ruthenium/validatornode/infrastructure/configuration" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/environment" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log/console" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/p2p" +) + +func main() { + settingsPath := flag.String("settings-path", environment.NewVariable("SETTINGS_PATH").GetStringValue("accessnode/settings.json"), "The settings file path") + flag.Parse() + settings, err := configuration.NewSettings(*settingsPath) + if err != nil { + panic(err.Error()) + } + logger := console.NewLogger(settings.Log().Level()) + validatorNeighbor, err := p2p.NewNeighbor(settings.Validator().Ip(), settings.Validator().Port(), time.Minute, console.NewFatalLogger()) + if err != nil { + logger.Fatal(fmt.Errorf("unable to find blockchain client: %w", err).Error()) + } + settingsBytes, err := validatorNeighbor.GetSettings() + if err != nil { + logger.Fatal(fmt.Errorf("unable to get protocol settings: %w", err).Error()) + } + var protocolSettings *validatorconfiguration.ProtocolSettings + err = json.Unmarshal(settingsBytes, &protocolSettings) + if err != nil { + logger.Fatal(fmt.Errorf("unable to unmarshal protocol settings: %w", err).Error()) + } + watch := clock.NewWatch() + node := presentation.NewNode(settings.Host().Port(), validatorNeighbor, protocolSettings, settings.Template().Path(), watch, logger) + logger.Info("host access node is running...") + logger.Fatal(node.Run().Error()) +} diff --git a/accessnode/presentation/api/index_controller.go b/accessnode/presentation/api/index_controller.go new file mode 100644 index 00000000..e8aa8471 --- /dev/null +++ b/accessnode/presentation/api/index_controller.go @@ -0,0 +1,29 @@ +package api + +import ( + "fmt" + "html/template" + "net/http" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type IndexController struct { + templatePath string + logger log.Logger +} + +func NewIndexController(templatePath string, logger log.Logger) *IndexController { + return &IndexController{templatePath, logger} +} + +func (controller *IndexController) GetIndex(writer http.ResponseWriter, _ *http.Request) { + t, err := template.ParseFiles(controller.templatePath) + if err != nil { + controller.logger.Error(fmt.Errorf("failed to parse the template: %w", err).Error()) + return + } + if err = t.Execute(writer, ""); err != nil { + controller.logger.Error(fmt.Errorf("failed to execute the template: %w", err).Error()) + } +} diff --git a/accessnode/presentation/api/payment/info_controller.go b/accessnode/presentation/api/payment/info_controller.go new file mode 100644 index 00000000..8e17bf56 --- /dev/null +++ b/accessnode/presentation/api/payment/info_controller.go @@ -0,0 +1,158 @@ +package payment + +import ( + "encoding/json" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "math" + "net/http" + "strconv" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type InfoController struct { + sender application.Sender + settings application.ProtocolSettingsProvider + watch application.TimeProvider + logger log.Logger +} + +func NewInfoController(sender application.Sender, settings application.ProtocolSettingsProvider, watch application.TimeProvider, logger log.Logger) *InfoController { + return &InfoController{sender, settings, watch, logger} +} + +func (controller *InfoController) GetTransactionInfo(writer http.ResponseWriter, req *http.Request) { + response := io.NewResponse(writer, controller.logger) + address := req.URL.Query().Get("address") + if address == "" { + errorMessage := "address is missing in amount request" + controller.logger.Error(errorMessage) + response.Write(http.StatusBadRequest, errorMessage) + return + } + requestValue := req.URL.Query().Get("value") + parsedValue, err := strconv.Atoi(requestValue) + if err != nil { + errorMessage := "failed to parse transaction value" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusBadRequest, errorMessage) + return + } + requestConsolidation := req.URL.Query().Get("consolidation") + isConsolidationRequired, err := strconv.ParseBool(requestConsolidation) + if err != nil { + errorMessage := "failed to parse consolidation value" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusBadRequest, errorMessage) + return + } + utxosBytes, err := controller.sender.GetUtxos(address) + if err != nil { + errorMessage := "failed to get UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var utxos []*ledger.Utxo + err = json.Unmarshal(utxosBytes, &utxos) + if err != nil { + errorMessage := "failed to unmarshal UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + genesisTimestamp, err := controller.sender.GetFirstBlockTimestamp() + if err != nil { + errorMessage := "failed to get genesis timestamp" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var selectedInputs []*ledger.InputInfo + now := controller.watch.Now().UnixNano() + nextBlockHeight := (now-genesisTimestamp)/controller.settings.ValidationTimestamp() + 1 + nextBlockTimestamp := genesisTimestamp + nextBlockHeight*controller.settings.ValidationTimestamp() + utxosByValue := make(map[uint64][]*ledger.InputInfo) + var walletBalance uint64 + var values []uint64 + for _, utxo := range utxos { + utxoValue := utxo.Value(nextBlockTimestamp, controller.settings.HalfLifeInNanoseconds(), controller.settings.IncomeBase(), controller.settings.IncomeLimit()) + if utxoValue == 0 { + continue + } + walletBalance += utxoValue + if isConsolidationRequired { + selectedInputs = append(selectedInputs, utxo.InputInfo) + } else { + if _, ok := utxosByValue[utxoValue]; !ok { + values = append(values, utxoValue) + } + utxosByValue[utxoValue] = append(utxosByValue[utxoValue], utxo.InputInfo) + } + } + value := uint64(parsedValue) + targetValue := value + controller.settings.MinimalTransactionFee() + if walletBalance < targetValue { + errorMessage := "insufficient wallet balance" + controller.logger.Error(errorMessage) + response.Write(http.StatusMethodNotAllowed, errorMessage) + return + } + + var inputsValue uint64 + if isConsolidationRequired { + inputsValue = walletBalance + } else if len(values) != 0 { + for inputsValue < targetValue { + closestValueIndex := findClosestValueIndex(targetValue, values) + closestValue := values[closestValueIndex] + if closestValue > targetValue { + inputsValue = closestValue + selectedInputs = []*ledger.InputInfo{utxosByValue[closestValue][0]} + break + } + values = append(values[:closestValueIndex], values[closestValueIndex+1:]...) + closestUtxos := utxosByValue[closestValue] + for i := 0; i < len(closestUtxos) && inputsValue < targetValue; i++ { + inputsValue += closestValue + selectedInputs = append(selectedInputs, closestUtxos[i]) + } + } + } + rest := inputsValue - targetValue + transactionInfo := &TransactionInfo{ + Rest: rest, + Inputs: selectedInputs, + Timestamp: now, + } + response.WriteJson(http.StatusOK, transactionInfo) +} + +func findClosestValueIndex(target uint64, values []uint64) int { + closestValueIndex := 0 + closestDifference := uint64(math.MaxUint64) + var isAValueGreaterThanTarget bool + for i, value := range values { + if isAValueGreaterThanTarget && value < target { + continue + } + var difference uint64 + if value < target { + difference = target - value + } else { + if !isAValueGreaterThanTarget { + closestDifference = uint64(math.MaxUint64) + } + isAValueGreaterThanTarget = true + difference = value - target + } + if difference < closestDifference { + closestValueIndex = i + closestDifference = difference + } + } + return closestValueIndex +} diff --git a/accessnode/presentation/api/payment/info_controller_test.go b/accessnode/presentation/api/payment/info_controller_test.go new file mode 100644 index 00000000..5f26d882 --- /dev/null +++ b/accessnode/presentation/api/payment/info_controller_test.go @@ -0,0 +1,306 @@ +package payment + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_GetTransactionInfo_InvalidAddress_ReturnsBadRequest(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_InvalidValue_ReturnsBadRequest(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_IsRegisteredNotProvided_ReturnsBadRequest(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_GetUtxosError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetUtxosCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyUtxos, _ := json.Marshal([]*ledger.Utxo{}) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, errors.New("") } + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_InsufficientWalletBalance_ReturnsMethodNotAllowed(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyUtxos, _ := json.Marshal([]*ledger.Utxo{}) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 405 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionInfo_ConsolidationNotRequiredAndOneUtxoIsGreater_ReturnsOneUtxo(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + + inputInfo1 := ledger.NewInputInfo(0, "") + inputInfo2 := ledger.NewInputInfo(1, "") + inputInfo3 := ledger.NewInputInfo(2, "") + output1 := ledger.NewOutput("", false, 1) + output2 := ledger.NewOutput("", false, 3) + output3 := ledger.NewOutput("", false, 7) + utxos := []*ledger.Utxo{ + ledger.NewUtxo(inputInfo1, output1, 1), + ledger.NewUtxo(inputInfo2, output2, 1), + ledger.NewUtxo(inputInfo3, output3, 1), + } + marshalledUtxos, _ := json.Marshal(utxos) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=3&consolidation=false", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + var transactionInfo *TransactionInfo + _ = json.Unmarshal(recorder.Body.Bytes(), &transactionInfo) + expectedInputsCount := 1 + actualInputsCount := len(transactionInfo.Inputs) + test.Assert(t, actualInputsCount == expectedInputsCount, fmt.Sprintf("Wrong inputs count. expected: %d actual: %d", expectedInputsCount, actualInputsCount)) +} + +func Test_GetTransactionInfo_ConsolidationNotRequiredAndNoUtxoIsGreater_ReturnsSomeUtxos(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + + inputInfo1 := ledger.NewInputInfo(0, "") + inputInfo2 := ledger.NewInputInfo(1, "") + inputInfo3 := ledger.NewInputInfo(2, "") + output1 := ledger.NewOutput("", false, 1) + output2 := ledger.NewOutput("", false, 2) + output3 := ledger.NewOutput("", false, 2) + utxos := []*ledger.Utxo{ + ledger.NewUtxo(inputInfo1, output1, 1), + ledger.NewUtxo(inputInfo2, output2, 1), + ledger.NewUtxo(inputInfo3, output3, 1), + } + marshalledUtxos, _ := json.Marshal(utxos) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=3&consolidation=false", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + var transactionInfo *TransactionInfo + _ = json.Unmarshal(recorder.Body.Bytes(), &transactionInfo) + expectedInputsCount := 2 + actualInputsCount := len(transactionInfo.Inputs) + test.Assert(t, actualInputsCount == expectedInputsCount, fmt.Sprintf("Wrong inputs count. expected: %d actual: %d", expectedInputsCount, actualInputsCount)) +} + +func Test_GetTransactionInfo_ConsolidationRequired_ReturnsAllUtxos(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + inputInfo1 := ledger.NewInputInfo(0, "") + inputInfo2 := ledger.NewInputInfo(2, "") + output1 := ledger.NewOutput("", false, 1) + output2 := ledger.NewOutput("", false, 2) + utxos := []*ledger.Utxo{ + ledger.NewUtxo(inputInfo1, output1, 1), + ledger.NewUtxo(inputInfo2, output2, 1), + } + marshalledUtxos, _ := json.Marshal(utxos) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewInfoController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=1&consolidation=true", "/"), nil) + + // Act + controller.GetTransactionInfo(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + var transactionInfo TransactionInfo + _ = json.Unmarshal(recorder.Body.Bytes(), &transactionInfo) + expectedInputsCount := 2 + actualInputsCount := len(transactionInfo.Inputs) + test.Assert(t, actualInputsCount == expectedInputsCount, fmt.Sprintf("Wrong inputs count. expected: %d actual: %d", expectedInputsCount, actualInputsCount)) +} diff --git a/accessnode/presentation/api/payment/progress_controller.go b/accessnode/presentation/api/payment/progress_controller.go new file mode 100644 index 00000000..5204aa59 --- /dev/null +++ b/accessnode/presentation/api/payment/progress_controller.go @@ -0,0 +1,124 @@ +package payment + +import ( + "encoding/json" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type ProgressController struct { + sender application.Sender + settings application.ProtocolSettingsProvider + watch application.TimeProvider + logger log.Logger +} + +func NewProgressController(sender application.Sender, settings application.ProtocolSettingsProvider, watch application.TimeProvider, logger log.Logger) *ProgressController { + return &ProgressController{sender, settings, watch, logger} +} + +func (controller *ProgressController) GetTransactionProgress(writer http.ResponseWriter, req *http.Request) { + response := io.NewResponse(writer, controller.logger) + decoder := json.NewDecoder(req.Body) + var searchedUtxo *ledger.Utxo + err := decoder.Decode(&searchedUtxo) + if err != nil { + errorMessage := "failed to decode utxo" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusBadRequest, errorMessage) + return + } + utxosBytes, err := controller.sender.GetUtxos(searchedUtxo.Address()) + if err != nil { + errorMessage := "failed to get UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var utxos []*ledger.Utxo + err = json.Unmarshal(utxosBytes, &utxos) + if err != nil { + errorMessage := "failed to unmarshal UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + genesisTimestamp, err := controller.sender.GetFirstBlockTimestamp() + now := controller.watch.Now().UnixNano() + currentBlockHeight := (now - genesisTimestamp) / controller.settings.ValidationTimestamp() + currentBlockTimestamp := genesisTimestamp + currentBlockHeight*controller.settings.ValidationTimestamp() + progressInfo := &ProgressInfo{ + CurrentBlockTimestamp: currentBlockTimestamp, + ValidationTimestamp: controller.settings.ValidationTimestamp(), + } + for _, utxo := range utxos { + if utxo.TransactionId() == searchedUtxo.TransactionId() && utxo.OutputIndex() == searchedUtxo.OutputIndex() { + progressInfo.TransactionStatus = "confirmed" + response.WriteJson(http.StatusOK, progressInfo) + return + } + } + if err != nil { + errorMessage := "failed to get genesis timestamp" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + blocksBytes, err := controller.sender.GetBlocks(uint64(currentBlockHeight)) + if err != nil { + errorMessage := "failed to get blocks" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var blocks []*ledger.Block + err = json.Unmarshal(blocksBytes, &blocks) + if err != nil { + errorMessage := "failed to unmarshal blocks" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + if len(blocks) == 0 { + errorMessage := "failed to get last block, get blocks returned an empty list" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + for _, validatedTransaction := range blocks[0].Transactions() { + if validatedTransaction.Id() == searchedUtxo.TransactionId() { + progressInfo.TransactionStatus = "validated" + response.WriteJson(http.StatusOK, progressInfo) + return + } + } + transactionsBytes, err := controller.sender.GetTransactions() + if err != nil { + errorMessage := "failed to get transactions" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var transactions []*ledger.Transaction + err = json.Unmarshal(transactionsBytes, &transactions) + if err != nil { + errorMessage := "failed to unmarshal transactions" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + for _, pendingTransaction := range transactions { + if pendingTransaction.Id() == searchedUtxo.TransactionId() { + progressInfo.TransactionStatus = "sent" + response.WriteJson(http.StatusOK, progressInfo) + return + } + } + progressInfo.TransactionStatus = "rejected" + response.WriteJson(http.StatusOK, progressInfo) +} diff --git a/accessnode/presentation/api/payment/progress_controller_test.go b/accessnode/presentation/api/payment/progress_controller_test.go new file mode 100644 index 00000000..aa1a637d --- /dev/null +++ b/accessnode/presentation/api/payment/progress_controller_test.go @@ -0,0 +1,315 @@ +package payment + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_GetTransactionProgress_UndecipherableUtxo_BadRequest(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + watchMock := new(application.TimeProviderMock) + logger := log.NewLoggerMock() + controller := NewProgressController(senderMock, settings, watchMock, logger) + data := "" + marshalledData, _ := json.Marshal(data) + body := bytes.NewReader(marshalledData) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetUtxosCalls()) != 0 + test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionProgress_GetUtxosError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + utxo := ledger.NewUtxo(&ledger.InputInfo{}, &ledger.Output{}, 0) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetUtxosCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionProgress_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, errors.New("") } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + utxo := ledger.NewUtxo(&ledger.InputInfo{}, &ledger.Output{}, 0) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionProgress_GetBlocksError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { return nil, errors.New("") } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + utxo := ledger.NewUtxo(&ledger.InputInfo{}, &ledger.Output{}, 0) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 && len(senderMock.GetBlocksCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionProgress_GetTransactionsError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*ledger.Block{ledger.NewBlock([32]byte{}, nil, nil, 0, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + senderMock.GetTransactionsFunc = func() ([]byte, error) { return nil, errors.New("") } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + utxo := ledger.NewUtxo(&ledger.InputInfo{}, &ledger.Output{}, 0) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 && len(senderMock.GetBlocksCalls()) == 1 && len(senderMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactionProgress_TransactionNotFound_ReturnsRejected(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*ledger.Block{ledger.NewBlock([32]byte{}, nil, nil, 0, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + senderMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledEmptyArray, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + utxo := ledger.NewUtxo(&ledger.InputInfo{}, &ledger.Output{}, 0) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 && len(senderMock.GetBlocksCalls()) == 1 && len(senderMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "rejected" + response := recorder.Body.Bytes() + var progressInfo *ProgressInfo + err := json.Unmarshal(response, &progressInfo) + fmt.Println(err) + actualStatus := progressInfo.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_GetTransactionProgress_UtxoFound_ReturnsConfirmed(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + transaction, _ := ledger.NewRewardTransaction("", false, 0, 0) + transactionId := transaction.Id() + inputInfo := ledger.NewInputInfo(0, transactionId) + utxo := ledger.NewUtxo(inputInfo, &ledger.Output{}, 0) + marshalledUtxos, _ := json.Marshal([]*ledger.Utxo{utxo}) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "confirmed" + response := recorder.Body.Bytes() + var progressInfo *ProgressInfo + err := json.Unmarshal(response, &progressInfo) + fmt.Println(err) + actualStatus := progressInfo.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_GetTransactionProgress_ValidatedTransactionFound_ReturnsValidated(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + transaction, _ := ledger.NewRewardTransaction("", false, 0, 0) + blocks := []*ledger.Block{ledger.NewBlock([32]byte{}, nil, nil, 0, []*ledger.Transaction{transaction})} + marshalledBlocks, _ := json.Marshal(blocks) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + outputIndex := 0 + utxo := ledger.NewUtxo(ledger.NewInputInfo(uint16(outputIndex), transaction.Id()), transaction.Outputs()[outputIndex], transaction.Timestamp()) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 && len(senderMock.GetBlocksCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "validated" + response := recorder.Body.Bytes() + var progressInfo *ProgressInfo + err := json.Unmarshal(response, &progressInfo) + fmt.Println(err) + actualStatus := progressInfo.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_GetTransactionProgress_PendingTransactionFound_ReturnsSent(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyArray := []byte{91, 93} + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + senderMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*ledger.Block{ledger.NewBlock([32]byte{}, nil, nil, 0, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + transaction, _ := ledger.NewRewardTransaction("", false, 0, 0) + transactions := []*ledger.Transaction{transaction} + marshalledTransactions, _ := json.Marshal(transactions) + senderMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledTransactions, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + controller := NewProgressController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + outputIndex := 0 + utxo := ledger.NewUtxo(ledger.NewInputInfo(uint16(outputIndex), transaction.Id()), transaction.Outputs()[outputIndex], transaction.Timestamp()) + marshalledUtxo, _ := json.Marshal(utxo) + body := bytes.NewReader(marshalledUtxo) + request := httptest.NewRequest(http.MethodPut, "/", body) + + // Act + controller.GetTransactionProgress(recorder, request) + + // Assert + areNeighborMethodsCalled := len(senderMock.GetUtxosCalls()) == 1 && len(senderMock.GetFirstBlockTimestampCalls()) == 1 && len(senderMock.GetBlocksCalls()) == 1 && len(senderMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "sent" + response := recorder.Body.Bytes() + var progressInfo *ProgressInfo + err := json.Unmarshal(response, &progressInfo) + fmt.Println(err) + actualStatus := progressInfo.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} diff --git a/src/ui/server/transaction/output/progress_info.go b/accessnode/presentation/api/payment/progress_info.go similarity index 92% rename from src/ui/server/transaction/output/progress_info.go rename to accessnode/presentation/api/payment/progress_info.go index 8dde849a..da88fc0a 100644 --- a/src/ui/server/transaction/output/progress_info.go +++ b/accessnode/presentation/api/payment/progress_info.go @@ -1,4 +1,4 @@ -package output +package payment type ProgressInfo struct { CurrentBlockTimestamp int64 `json:"current_block_timestamp"` diff --git a/accessnode/presentation/api/payment/transaction_controller.go b/accessnode/presentation/api/payment/transaction_controller.go new file mode 100644 index 00000000..41c6066a --- /dev/null +++ b/accessnode/presentation/api/payment/transaction_controller.go @@ -0,0 +1,50 @@ +package payment + +import ( + "encoding/json" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type TransactionController struct { + sender application.Sender + logger log.Logger +} + +func NewTransactionController(sender application.Sender, logger log.Logger) *TransactionController { + return &TransactionController{sender, logger} +} + +func (controller *TransactionController) PostTransaction(writer http.ResponseWriter, req *http.Request) { + response := io.NewResponse(writer, controller.logger) + decoder := json.NewDecoder(req.Body) + var transaction *ledger.Transaction + err := decoder.Decode(&transaction) + if err != nil { + errorMessage := "failed to decode transaction" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusBadRequest, errorMessage) + return + } + transactionRequest := ledger.NewTransactionRequest(transaction, controller.sender.Target()) + marshaledTransaction, err := json.Marshal(transactionRequest) + if err != nil { + errorMessage := "failed to marshal transaction request" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + err = controller.sender.AddTransaction(marshaledTransaction) + if err != nil { + errorMessage := "failed to add transaction" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + response.Write(http.StatusCreated, "success") +} diff --git a/accessnode/presentation/api/payment/transaction_controller_test.go b/accessnode/presentation/api/payment/transaction_controller_test.go new file mode 100644 index 00000000..5b541792 --- /dev/null +++ b/accessnode/presentation/api/payment/transaction_controller_test.go @@ -0,0 +1,84 @@ +package payment + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + "net/http/httptest" + "testing" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_PostTransaction_UndecipherableTransaction_BadRequest(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + logger := log.NewLoggerMock() + controller := NewTransactionController(senderMock, logger) + marshalledData, _ := json.Marshal("") + body := bytes.NewReader(marshalledData) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", body) + + // Act + controller.PostTransaction(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.AddTransactionCalls()) != 0 + test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_PostTransaction_NodeError_InternalServerError(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + target := "0.0.0.0:0" + senderMock.TargetFunc = func() string { return target } + senderMock.AddTransactionFunc = func([]byte) error { return errors.New("") } + logger := log.NewLoggerMock() + controller := NewTransactionController(senderMock, logger) + transactionRequest, _ := ledger.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transactionRequest) + body := bytes.NewReader(marshalledTransaction) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", body) + + // Act + controller.PostTransaction(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.AddTransactionCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_PostTransaction_ValidTransaction_NeighborMethodCalled(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + target := "0.0.0.0:0" + senderMock.TargetFunc = func() string { return target } + senderMock.AddTransactionFunc = func([]byte) error { return nil } + logger := log.NewLoggerMock() + controller := NewTransactionController(senderMock, logger) + transactionRequest, _ := ledger.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transactionRequest) + body := bytes.NewReader(marshalledTransaction) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", body) + + // Act + controller.PostTransaction(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.AddTransactionCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 201 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} diff --git a/accessnode/presentation/api/payment/transaction_info.go b/accessnode/presentation/api/payment/transaction_info.go new file mode 100644 index 00000000..3a08227c --- /dev/null +++ b/accessnode/presentation/api/payment/transaction_info.go @@ -0,0 +1,11 @@ +package payment + +import ( + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" +) + +type TransactionInfo struct { + Inputs []*ledger.InputInfo `json:"inputs"` + Rest uint64 `json:"rest"` + Timestamp int64 `json:"timestamp"` +} diff --git a/accessnode/presentation/api/payment/transactions_controller.go b/accessnode/presentation/api/payment/transactions_controller.go new file mode 100644 index 00000000..abcf7cfb --- /dev/null +++ b/accessnode/presentation/api/payment/transactions_controller.go @@ -0,0 +1,32 @@ +package payment + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type TransactionsController struct { + sender application.Sender + logger log.Logger +} + +func NewTransactionsController(sender application.Sender, logger log.Logger) *TransactionsController { + return &TransactionsController{sender, logger} +} + +func (controller *TransactionsController) GetTransactions(writer http.ResponseWriter, _ *http.Request) { + response := io.NewResponse(writer, controller.logger) + transactions, err := controller.sender.GetTransactions() + if err != nil { + errorMessage := "failed to get transactions" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + writer.Header().Add("Content-Type", "application/json") + response.Write(http.StatusOK, string(transactions[:])) +} diff --git a/accessnode/presentation/api/payment/transactions_controller_test.go b/accessnode/presentation/api/payment/transactions_controller_test.go new file mode 100644 index 00000000..e03189eb --- /dev/null +++ b/accessnode/presentation/api/payment/transactions_controller_test.go @@ -0,0 +1,51 @@ +package payment + +import ( + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + "net/http/httptest" + "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_GetTransactions_NodeError_InternalServerError(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + senderMock.GetTransactionsFunc = func() ([]byte, error) { return nil, errors.New("") } + logger := log.NewLoggerMock() + controller := NewTransactionsController(senderMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + + // Act + controller.GetTransactions(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetTransactionsCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetTransactions_ValidRequest_NeighborMethodCalled(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + senderMock.GetTransactionsFunc = func() ([]byte, error) { return nil, nil } + logger := log.NewLoggerMock() + controller := NewTransactionsController(senderMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + + // Act + controller.GetTransactions(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetTransactionsCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} diff --git a/templates/index.html b/accessnode/presentation/api/template.html similarity index 74% rename from templates/index.html rename to accessnode/presentation/api/template.html index 50f61ce2..69962c52 100644 --- a/templates/index.html +++ b/accessnode/presentation/api/template.html @@ -5,11 +5,28 @@ Wallet - - - - + integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" + crossorigin="anonymous"> + + + +

Wallet

@@ -36,14 +53,14 @@

Wallet

- +
-

Send Tokens

+

Send Coins

@@ -68,7 +85,7 @@

Send Tokens

- +
@@ -112,13 +129,14 @@

Transactions Pool

$(function () { let lastRestUtxo; - $("#send_tokens_button").click(function () { + $("#send_coins_button").click(function () { if (!keyPair) { - alert("The private key must be provided to send tokens") + alert("The private key must be provided to send coins") return } const senderAddress = $("#sender_address").val(); + const recipientAddress = $("#recipient_address").val(); const atoms = $("#amount").val(); const result = atomsToParticles(atoms, 100000000); if (result.err) { @@ -136,66 +154,6 @@

Transactions Pool

} const data = {"address": senderAddress, "value": value, "consolidation": isConsolidationRequested}; - function send(response) { - let inputs = []; - for (let i = 0; i < response.inputs.length; i++) { - let input = response.inputs[i]; - const hash = CryptoJS.SHA256(JSON.stringify(input)).toString(CryptoJS.enc.Hex); - const signature = keyPair.sign(hash); - const signatureHex = getSignatureHex(signature); - inputs[i] = { - "output_index": input.output_index, - "transaction_id": input.transaction_id, - "public_key": publicKeyString, - "signature": signatureHex, - }; - } - - const recipientAddress = $("#recipient_address").val(); - const spend = { - "address": recipientAddress, - "is_yielding": false, - "value": value, - } - const rest = { - "address": senderAddress, - "is_yielding": isIncomeUpdateRequested, - "value": response.rest, - } - const transaction = { - "inputs": inputs, - "outputs": [spend, rest], - "timestamp": response.timestamp, - }; - transaction.id = CryptoJS.SHA256(JSON.stringify(transaction)).toString(CryptoJS.enc.Hex); - - $.ajax({ - url: "/transaction", - type: "POST", - contentType: "application/json", - data: JSON.stringify(transaction), - success: function (response) { - if (response === "success") { - alert("Send success"); - lastRestUtxo = { - "address": rest.address, - "timestamp": transaction.timestamp, - "is_yielding": rest.is_yielding, - "output_index": 1, - "transaction_id": transaction.id, - "value": rest.value, - } - } else { - alert("Send failed: " + response) - } - }, - error: function (response) { - console.error(response); - alert("Send failed: " + response.responseText); - } - }) - } - $.ajax({ url: "/transaction/info", type: "GET", @@ -203,21 +161,94 @@

Transactions Pool

dataType: 'json', data: data, success: function (response) { - if (!confirm("Are you sure to send?")) { + if (!confirm("Are you sure you want to send " + atoms + " coins to " + recipientAddress + "?")) { alert("Canceled"); return } - send(response); + send(data); }, error: function (response) { console.error(response); alert("Send failed: " + response.responseText); } }) + + function send(data) { + $.ajax({ + url: "/transaction/info", + type: "GET", + contentType: "application/json", + dataType: 'json', + data: data, + success: function (response) { + let inputs = []; + for (let i = 0; i < response.inputs.length; i++) { + let input = response.inputs[i]; + const hash = CryptoJS.SHA256(JSON.stringify(input)).toString(CryptoJS.enc.Hex); + const signature = keyPair.sign(hash); + const signatureHex = getSignatureHex(signature); + inputs[i] = { + "output_index": input.output_index, + "transaction_id": input.transaction_id, + "public_key": publicKeyString, + "signature": signatureHex, + }; + } + + const recipientAddress = $("#recipient_address").val(); + const spend = { + "address": recipientAddress, + "is_yielding": false, + "value": value, + } + const rest = { + "address": senderAddress, + "is_yielding": isIncomeUpdateRequested, + "value": response.rest, + } + const transaction = { + "inputs": inputs, + "outputs": [spend, rest], + "timestamp": response.timestamp, + }; + transaction.id = CryptoJS.SHA256(JSON.stringify(transaction)).toString(CryptoJS.enc.Hex); + + $.ajax({ + url: "/transaction", + type: "POST", + contentType: "application/json", + data: JSON.stringify(transaction), + success: function (response) { + if (response === "success") { + alert("Send success"); + lastRestUtxo = { + "address": rest.address, + "timestamp": transaction.timestamp, + "is_yielding": rest.is_yielding, + "output_index": 1, + "transaction_id": transaction.id, + "value": rest.value, + } + } else { + alert("Send failed: " + response) + } + }, + error: function (response) { + console.error(response); + alert("Send failed: " + response.responseText); + } + }) + }, + error: function (response) { + console.error(response); + alert("Send failed: " + response.responseText); + } + }) + } }) - setInterval(refresh_amount, 100) - setInterval(refresh_transactions, 100) + setInterval(refresh_amount, 1000) + setInterval(refresh_transactions, 200) setInterval(refresh_progress, 100) function refresh_amount() { @@ -441,7 +472,7 @@

Transactions Pool

} .form-field { - width: calc(100% - 110px);; + width: calc(100% - 110px); } button { diff --git a/accessnode/presentation/api/wallet/address_controller.go b/accessnode/presentation/api/wallet/address_controller.go new file mode 100644 index 00000000..1ad9293d --- /dev/null +++ b/accessnode/presentation/api/wallet/address_controller.go @@ -0,0 +1,32 @@ +package wallet + +import ( + "fmt" + "net/http" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type AddressController struct { + logger log.Logger +} + +func NewAddressController(logger log.Logger) *AddressController { + return &AddressController{logger} +} + +func (controller *AddressController) GetWalletAddress(writer http.ResponseWriter, req *http.Request) { + response := io.NewResponse(writer, controller.logger) + publicKeyString := req.URL.Query().Get("publicKey") + publicKey, err := encryption.NewPublicKeyFromHex(publicKeyString) + if err != nil { + errorMessage := "failed to decode public key" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusBadRequest, errorMessage) + return + } + address := publicKey.Address() + response.WriteJson(http.StatusOK, address) +} diff --git a/accessnode/presentation/api/wallet/address_controller_test.go b/accessnode/presentation/api/wallet/address_controller_test.go new file mode 100644 index 00000000..47986260 --- /dev/null +++ b/accessnode/presentation/api/wallet/address_controller_test.go @@ -0,0 +1,41 @@ +package wallet + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_GetWalletAddress_InvalidPublicKey_ReturnsBadRequest(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + controller := NewAddressController(logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?publicKey=invalidPublicKey", "/"), nil) + + // Act + controller.GetWalletAddress(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetWalletAddress_ValidRequest_ReturnsAddress(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + controller := NewAddressController(logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?publicKey=%s", "/", test.PublicKey), nil) + + // Act + controller.GetWalletAddress(recorder, request) + + // Assert + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} diff --git a/accessnode/presentation/api/wallet/amount_controller.go b/accessnode/presentation/api/wallet/amount_controller.go new file mode 100644 index 00000000..cd8a9316 --- /dev/null +++ b/accessnode/presentation/api/wallet/amount_controller.go @@ -0,0 +1,56 @@ +package wallet + +import ( + "encoding/json" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + + "github.com/my-cloud/ruthenium/accessnode/infrastructure/io" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type AmountController struct { + sender application.Sender + settings application.ProtocolSettingsProvider + watch application.TimeProvider + logger log.Logger +} + +func NewAmountController(sender application.Sender, settings application.ProtocolSettingsProvider, watch application.TimeProvider, logger log.Logger) *AmountController { + return &AmountController{sender, settings, watch, logger} +} + +func (controller *AmountController) GetWalletAmount(writer http.ResponseWriter, req *http.Request) { + response := io.NewResponse(writer, controller.logger) + address := req.URL.Query().Get("address") + if address == "" { + errorMessage := "address is missing in amount request" + controller.logger.Error(errorMessage) + response.Write(http.StatusBadRequest, errorMessage) + return + } + utxosBytes, err := controller.sender.GetUtxos(address) + if err != nil { + errorMessage := "failed to get UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var utxos []*ledger.Utxo + err = json.Unmarshal(utxosBytes, &utxos) + if err != nil { + errorMessage := "failed to unmarshal UTXOs" + controller.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) + response.Write(http.StatusInternalServerError, errorMessage) + return + } + var balance uint64 + for _, utxo := range utxos { + now := controller.watch.Now().UnixNano() + balance += utxo.Value(now, controller.settings.HalfLifeInNanoseconds(), controller.settings.IncomeBase(), controller.settings.IncomeLimit()) + } + amount := float64(balance) / float64(controller.settings.SmallestUnitsPerCoin()) + response.WriteJson(http.StatusOK, amount) +} diff --git a/accessnode/presentation/api/wallet/amount_controller_test.go b/accessnode/presentation/api/wallet/amount_controller_test.go new file mode 100644 index 00000000..5a73c19d --- /dev/null +++ b/accessnode/presentation/api/wallet/amount_controller_test.go @@ -0,0 +1,90 @@ +package wallet + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_GetWalletAmount_InvalidAddress_ReturnsBadRequest(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + controller := NewAmountController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/", nil) + + // Act + controller.GetWalletAmount(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetWalletAmount_GetUtxosError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } + watchMock := new(application.TimeProviderMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + controller := NewAmountController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", "/"), nil) + + // Act + controller.GetWalletAmount(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetUtxosCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_GetWalletAmount_ValidRequest_ReturnsAmount(t *testing.T) { + // Arrange + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + marshalledEmptyUtxos, _ := json.Marshal([]*ledger.Utxo{}) + senderMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(application.ProtocolSettingsProviderMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseFunc = func() uint64 { return 0 } + settings.IncomeLimitFunc = func() uint64 { return 0 } + settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } + controller := NewAmountController(senderMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", "/"), nil) + + // Act + controller.GetWalletAmount(recorder, request) + + // Assert + isNeighborMethodCalled := len(senderMock.GetUtxosCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} diff --git a/accessnode/presentation/node.go b/accessnode/presentation/node.go new file mode 100644 index 00000000..4e1feec4 --- /dev/null +++ b/accessnode/presentation/node.go @@ -0,0 +1,39 @@ +package presentation + +import ( + "github.com/gin-gonic/gin" + "github.com/my-cloud/ruthenium/accessnode/presentation/api" + "github.com/my-cloud/ruthenium/accessnode/presentation/api/payment" + "github.com/my-cloud/ruthenium/accessnode/presentation/api/wallet" + "github.com/my-cloud/ruthenium/validatornode/application" + "github.com/my-cloud/ruthenium/validatornode/domain/clock" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log/console" +) + +type Node struct { + port string + rooter *gin.Engine +} + +func NewNode(port string, sender application.Sender, settings application.ProtocolSettingsProvider, templatePath string, watch *clock.Watch, logger *console.Logger) *Node { + rooter := gin.Default() + indexController := api.NewIndexController(templatePath, logger) + transactionController := payment.NewTransactionController(sender, logger) + transactionsController := payment.NewTransactionsController(sender, logger) + infoController := payment.NewInfoController(sender, settings, watch, logger) + progressController := payment.NewProgressController(sender, settings, watch, logger) + addressController := wallet.NewAddressController(logger) + amountController := wallet.NewAmountController(sender, settings, watch, logger) + rooter.GET("/", func(c *gin.Context) { indexController.GetIndex(c.Writer, c.Request) }) + rooter.POST("/transaction", func(c *gin.Context) { transactionController.PostTransaction(c.Writer, c.Request) }) + rooter.GET("/transactions", func(c *gin.Context) { transactionsController.GetTransactions(c.Writer, c.Request) }) + rooter.GET("/transaction/info", func(c *gin.Context) { infoController.GetTransactionInfo(c.Writer, c.Request) }) + rooter.PUT("/transaction/output/progress", func(c *gin.Context) { progressController.GetTransactionProgress(c.Writer, c.Request) }) + rooter.GET("/wallet/address", func(c *gin.Context) { addressController.GetWalletAddress(c.Writer, c.Request) }) + rooter.GET("/wallet/amount", func(c *gin.Context) { amountController.GetWalletAmount(c.Writer, c.Request) }) + return &Node{port, rooter} +} + +func (node *Node) Run() error { + return node.rooter.Run(":" + node.port) +} diff --git a/accessnode/presentation/node_test.go b/accessnode/presentation/node_test.go new file mode 100644 index 00000000..c7b0213a --- /dev/null +++ b/accessnode/presentation/node_test.go @@ -0,0 +1,15 @@ +package presentation + +import ( + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" + "testing" +) + +func Test_CalculateFee_UnknownTransactionId_ReturnsError(t *testing.T) { + // Arrange + // Act + node := NewNode("", nil, nil, "", nil, nil) + + // Assert + test.Assert(t, node != nil, "node is nil whereas it should not") +} diff --git a/accessnode/settings.json b/accessnode/settings.json new file mode 100644 index 00000000..4b70afde --- /dev/null +++ b/accessnode/settings.json @@ -0,0 +1,15 @@ +{ + "host": { + "port": 8080 + }, + "template": { + "path": "accessnode/presentation/api/template.html" + }, + "validator": { + "ip": "127.0.0.1", + "port": 10600 + }, + "log": { + "level": "info" + } +} diff --git a/config/seeds.json b/config/seeds.json deleted file mode 100644 index a97eb58f..00000000 --- a/config/seeds.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - "seed-hael.ruthenium.my-cloud.me:10600", - "seed-styx.ruthenium.my-cloud.me:10600" -] diff --git a/config/settings.json b/config/settings.json deleted file mode 100644 index eeb5d824..00000000 --- a/config/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "blocksCountLimit": 1440, - "genesisAmount": 5000000000000, - "halfLifeInDays": 373.59, - "incomeBase": 100000000000, - "incomeLimit": 5000000000000, - "maxOutboundsCount": 8, - "minimalTransactionFee": 1000, - "smallestUnitsPerCoin": 100000000, - "synchronizationIntervalInSeconds": 10, - "validationIntervalInSeconds": 60, - "validationTimeoutInSeconds": 5, - "verificationsCountPerValidation": 6 -} diff --git a/deploy/helm/README.md b/deploy/helm/README.md index 9249586f..d374cfce 100644 --- a/deploy/helm/README.md +++ b/deploy/helm/README.md @@ -24,64 +24,64 @@ $ helm install ruthenium --set secrets[0].data.privateKey= ## Values -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| app.containers[0].annotations | map | {} | any relevant annotation | -| app.containers[0].args | list | [] | any application argument | -| app.containers[0].autoReload | string | "true" | specifies if the container should be restarted on each update | -| app.containers[0].command[0] | string | "/app/node" | application binary path | -| app.containers[0].env | map | {} | any environment variable `ENV_NAME: value` | -| app.containers[0].health.liveness.initialDelaySeconds | int | 40 | kubernetes liveness initialDelaySeconds | -| app.containers[0].health.liveness.periodSeconds | int | 5 | kubernetes liveness periodSeconds | -| app.containers[0].health.readiness.initialDelaySeconds | int | 30 | kubernetes readiness initialDelaySeconds | -| app.containers[0].health.readiness.periodSeconds | int | 1 | kubernetes readiness periodSeconds | -| app.containers[0].health.type | string | "grpc" | kubernetes healthcheck type | -| app.containers[0].image.name | string | "ghcr.io/my-cloud/ruthenium" | container image | -| app.containers[0].image.pullPolicy | string | "IfNotPresent" | container pull policy | -| app.containers[0].image.tagOverride | string | "latest" | container tag | -| app.containers[0].name | string | "node" | container name | -| app.containers[0].resources.limits.memory | string | "512Mi" | kubernetes resources limits memory | -| app.containers[0].resources.requests.memory | string | "128Mi" | kubernetes resources requests memory | -| app.containers[0].secret.ruthenium.PRIVATE_KEY | string | "privateKey" | Gets the `privateKey` key of the `ruthenium` secret and populate a container environment variable `PRIVATE_KEY` with the secret value | -| app.containers[0].service[0].port | string | "8106" | kubernetes service port | -| app.containers[0].service[0].protocol | string | "TCP" | kubernetes service protocol | -| app.containers[0].service[0].targetPort | string | "8106" | kubernetes service container listening port | -| app.containers[0].storage.data | string | "/tmp" | will mount the `data` volume in the specified path | -| app.containers[1].annotations | map | {} | any relevant annotation | -| app.containers[1].autoReload | string | "true" | specifies if the container should be restarted on each update | -| app.containers[1].command[0] | string | "/app/ui" | any application argument | -| app.containers[1].env | map | {} | any environment variable ENV_NAME: value | -| app.containers[1].env.HOST_IP | string | "127.0.0.1" | specifies the `HOST_IP` environment variable with the node IP address | -| app.containers[1].health.liveness.initialDelaySeconds | int | 120 | kubernetes liveness initialDelaySeconds | -| app.containers[1].health.liveness.path | string | "/health/liveness" | kubernetes liveness path | -| app.containers[1].health.liveness.periodSeconds | int | 5 | kubernetes readiness periodSeconds | -| app.containers[1].health.readiness.initialDelaySeconds | int | 30 | kubernetes readiness initialDelaySeconds | -| app.containers[1].health.readiness.path | string | "/health/readiness" | kubernetes readiness path | -| app.containers[1].health.readiness.periodSeconds | int | 1 | kubernetes readiness periodSeconds | -| app.containers[1].health.type | string | "httpGet" | kubernetes healthcheck type | -| app.containers[1].image.name | string | "ghcr.io/my-cloud/ruthenium" | container image | -| app.containers[1].image.pullPolicy | string | "IfNotPresent" | container pull policy | -| app.containers[1].image.tagOverride | string | "latest" | container tag override | -| app.containers[1].name | string | "ui" | container name | -| app.containers[1].resources.limits.memory | string | "512Mi" | kubernetes resources limits memory | -| app.containers[1].resources.requests.memory | string | "128Mi" | kubernetes resources requests memory | -| app.containers[1].secret.ruthenium.PRIVATE_KEY | string | "privateKey" | Gets the privateKey key of the ruthenium secret and populate a container environment variable PRIVATE_KEY with the secret value | -| app.containers[1].service[0].port | string | "80" | service port | -| app.containers[1].service[0].protocol | string | "TCP" | service protocol | -| app.containers[1].service[0].targetPort | string | "8080" | kubernetes service container listening port | -| app.containers[1].storage.data | string | "/data" | will mount the `data` volume in the specified path | -| app.replicas | int | 1 | Number of replicas. it is useless to set over `1` due to Ruthenium fundamental mechanism | -| app.storage.data.accessModes[0] | string | "ReadWriteMany" | storage access mode | -| app.storage.data.size | string | "128M" | storage size | -| app.storage.data.storageClass | string | "kube-data" | storage class | -| app.type | string | "statefulset" | kubernetes kind (only `statefulset` is available for now) | -| app.volumes[0].emptyDir | map | {} | volume type | -| app.volumes[0].name | string | "shared-data" | volume name | -| global.autoReload | string | "true" | auto reload set globally (useful in meta deployments) | -| global.image.pullPolicy | string | "Always" | image pull policies set globally (useful in meta deployments) | -| global.registries | list | [] | list of registries (useful with private registries) | -| secrets[0].annotations | map | {} | secret annotation | -| secrets[0].data.privateKey | string | null | secret private key | -| secrets[0].name | string | "ruthenium" | secret name | -| url.domains[0] | string | "ruthenium.example.com" | domain name on which ruthenium would be available | +| Key | Type | Default | Description | +|--------------------------------------------------------|------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| app.containers[0].annotations | map | {} | any relevant annotation | +| app.containers[0].args | list | [] | any application argument | +| app.containers[0].autoReload | string | "true" | specifies if the container should be restarted on each update | +| app.containers[0].command[0] | string | "/app/validatornode" | application binary path | +| app.containers[0].env | map | {} | any environment variable `ENV_NAME: value` | +| app.containers[0].health.liveness.initialDelaySeconds | int | 40 | kubernetes liveness initialDelaySeconds | +| app.containers[0].health.liveness.periodSeconds | int | 5 | kubernetes liveness periodSeconds | +| app.containers[0].health.readiness.initialDelaySeconds | int | 30 | kubernetes readiness initialDelaySeconds | +| app.containers[0].health.readiness.periodSeconds | int | 1 | kubernetes readiness periodSeconds | +| app.containers[0].health.type | string | "grpc" | kubernetes healthcheck type | +| app.containers[0].image.name | string | "ghcr.io/my-cloud/ruthenium" | container image | +| app.containers[0].image.pullPolicy | string | "IfNotPresent" | container pull policy | +| app.containers[0].image.tagOverride | string | "latest" | container tag | +| app.containers[0].name | string | "node" | container name | +| app.containers[0].resources.limits.memory | string | "512Mi" | kubernetes resources limits memory | +| app.containers[0].resources.requests.memory | string | "128Mi" | kubernetes resources requests memory | +| app.containers[0].secret.ruthenium.PRIVATE_KEY | string | "privateKey" | Gets the `privateKey` key of the `ruthenium` secret and populate a container environment variable `PRIVATE_KEY` with the secret value | +| app.containers[0].service[0].port | string | "8106" | kubernetes service port | +| app.containers[0].service[0].protocol | string | "TCP" | kubernetes service protocol | +| app.containers[0].service[0].targetPort | string | "8106" | kubernetes service container listening port | +| app.containers[0].storage.data | string | "/tmp" | will mount the `data` volume in the specified path | +| app.containers[1].annotations | map | {} | any relevant annotation | +| app.containers[1].autoReload | string | "true" | specifies if the container should be restarted on each update | +| app.containers[1].command[0] | string | "/app/ui" | any application argument | +| app.containers[1].env | map | {} | any environment variable ENV_NAME: value | +| app.containers[1].env.VALIDATOR_IP | string | "127.0.0.1" | specifies the `VALIDATOR_IP` environment variable with the validator node IP address | +| app.containers[1].health.liveness.initialDelaySeconds | int | 120 | kubernetes liveness initialDelaySeconds | +| app.containers[1].health.liveness.path | string | "/health/liveness" | kubernetes liveness path | +| app.containers[1].health.liveness.periodSeconds | int | 5 | kubernetes readiness periodSeconds | +| app.containers[1].health.readiness.initialDelaySeconds | int | 30 | kubernetes readiness initialDelaySeconds | +| app.containers[1].health.readiness.path | string | "/health/readiness" | kubernetes readiness path | +| app.containers[1].health.readiness.periodSeconds | int | 1 | kubernetes readiness periodSeconds | +| app.containers[1].health.type | string | "httpGet" | kubernetes healthcheck type | +| app.containers[1].image.name | string | "ghcr.io/my-cloud/ruthenium" | container image | +| app.containers[1].image.pullPolicy | string | "IfNotPresent" | container pull policy | +| app.containers[1].image.tagOverride | string | "latest" | container tag override | +| app.containers[1].name | string | "ui" | container name | +| app.containers[1].resources.limits.memory | string | "512Mi" | kubernetes resources limits memory | +| app.containers[1].resources.requests.memory | string | "128Mi" | kubernetes resources requests memory | +| app.containers[1].secret.ruthenium.PRIVATE_KEY | string | "privateKey" | Gets the privateKey key of the ruthenium secret and populate a container environment variable PRIVATE_KEY with the secret value | +| app.containers[1].service[0].port | string | "80" | service port | +| app.containers[1].service[0].protocol | string | "TCP" | service protocol | +| app.containers[1].service[0].targetPort | string | "8080" | kubernetes service container listening port | +| app.containers[1].storage.data | string | "/data" | will mount the `data` volume in the specified path | +| app.replicas | int | 1 | Number of replicas. it is useless to set over `1` due to Ruthenium fundamental mechanism | +| app.storage.data.accessModes[0] | string | "ReadWriteMany" | storage access mode | +| app.storage.data.size | string | "128M" | storage size | +| app.storage.data.storageClass | string | "kube-data" | storage class | +| app.type | string | "statefulset" | kubernetes kind (only `statefulset` is available for now) | +| app.volumes[0].emptyDir | map | {} | volume type | +| app.volumes[0].name | string | "shared-data" | volume name | +| global.autoReload | string | "true" | auto reload set globally (useful in meta deployments) | +| global.image.pullPolicy | string | "Always" | image pull policies set globally (useful in meta deployments) | +| global.registries | list | [] | list of registries (useful with private registries) | +| secrets[0].annotations | map | {} | secret annotation | +| secrets[0].data.privateKey | string | null | secret private key | +| secrets[0].name | string | "ruthenium" | secret name | +| url.domains[0] | string | "ruthenium.example.com" | domain name on which ruthenium would be available | diff --git a/deploy/helm/templates/statefulset.yaml b/deploy/helm/templates/statefulset.yaml index 4ee2d653..d1a208dd 100644 --- a/deploy/helm/templates/statefulset.yaml +++ b/deploy/helm/templates/statefulset.yaml @@ -64,8 +64,12 @@ spec: resources: requests: memory: {{ $container.resources.requests.memory }} + cpu: {{ $container.resources.requests.cpu }} + ephemeral-storage: {{ $container.resources.requests.storage }} limits: memory: {{ $container.resources.limits.memory }} + cpu: {{ $container.resources.limits.cpu }} + ephemeral-storage: {{ $container.resources.limits.storage }} ports: {{- range $container.service }} - name: {{ $container.name }} @@ -101,6 +105,7 @@ spec: mountPath: {{ $mountPath }} {{- end }} {{- end }} + automountServiceAccountToken: false securityContext: fsGroup: 1000 restartPolicy: Always diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index f3a26111..e1c4caf7 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -21,18 +21,18 @@ app: - name: shared-data emptyDir: {} containers: - - name: node + - name: validatornode image: name: ghcr.io/my-cloud/ruthenium tagOverride: latest #pullPolicy: "IfNotPresent" #autoReload: "true" - command: [/app/node] + command: [/app/validatornode] args: [] service: - protocol: TCP - port: "8106" - targetPort: "8106" + port: "10600" + targetPort: "10600" annotations: health: # type: grpc # can be either: grpc httpGet tcpSocket @@ -45,8 +45,12 @@ app: resources: requests: memory: "128Mi" + cpu: 0.5 + storage: 2Gi limits: memory: "512Mi" + cpu: 2 + storage: 4Gi storage: data: /tmp secret: @@ -54,13 +58,13 @@ app: PRIVATE_KEY: privateKey env: - - name: ui + - name: accessnode image: name: ghcr.io/my-cloud/ruthenium tagOverride: latest #pullPolicy: "IfNotPresent" #autoReload: "true" - command: [/app/ui] + command: [/app/accessnode] service: - protocol: TCP port: "80" @@ -79,8 +83,12 @@ app: resources: requests: memory: "128Mi" + cpu: 0.5 + storage: 2Gi limits: memory: "512Mi" + cpu: 2 + storage: 4Gi storage: data: /data secret: diff --git a/go.mod b/go.mod index bc0d5791..af6b6041 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/btcsuite/btcd v0.24.0 github.com/btcsuite/btcd/btcutil v1.1.5 github.com/ethereum/go-ethereum v1.13.15 + github.com/gin-gonic/gin v1.9.1 github.com/leprosus/golang-p2p v1.3.11 github.com/tyler-smith/go-bip39 v1.1.0 ) @@ -16,6 +17,8 @@ require ( github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect @@ -23,20 +26,40 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.2.4 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/supranational/blst v0.3.11 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.18.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.15.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 06a2252a..7ddd8ca7 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,14 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cockroachdb/errors v1.8.1 h1:A5+txlVZfOqFBDa4mGz2bUWSp0aHElvHX2bKkdbQu+Y= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= @@ -70,11 +76,26 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -85,12 +106,16 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= @@ -108,16 +133,24 @@ github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7Bd github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/leprosus/golang-p2p v1.3.11 h1:qHboNgdw6L/TArNxa0iGi+qNP88yT6YS7qR2dU4Vc+M= github.com/leprosus/golang-p2p v1.3.11/go.mod h1:DrFX32uwEX8J89s/sQ2PxjQf+ppElDZ9Mkl7L30BGHQ= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= @@ -125,6 +158,11 @@ github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjU github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -135,6 +173,8 @@ github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5 github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -150,7 +190,15 @@ github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1 github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= @@ -160,10 +208,17 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -178,6 +233,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -191,8 +248,10 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= @@ -201,6 +260,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= @@ -213,8 +273,11 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -224,5 +287,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/sonar-project.properties b/sonar-project.properties index 32c72ebb..e8097738 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,11 +1,10 @@ sonar.projectKey=my-cloud_ruthenium sonar.organization=my-cloud -sonar.sources=src - -sonar.tests=test +sonar.sources=. +sonar.tests=. sonar.test.inclusions=**/*_test.go sonar.go.coverage.reportPaths=coverage.out sonar.go.tests.reportPaths=report.json -sonar.coverage.exclusions=**/proof_of_humanity.go, **/main.go, **/net/** -sonar.exclusions=**/proof_of_humanity.go +sonar.coverage.exclusions=**/main.go, **/net/**, **/configuration/*settings.go +sonar.exclusions=**/proof_of_humanity.go, **/*_mock.go diff --git a/src/config/settings.go b/src/config/settings.go deleted file mode 100644 index 569c4ad2..00000000 --- a/src/config/settings.go +++ /dev/null @@ -1,140 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "io" - "os" - "time" -) - -type settingsDto struct { - BlocksCountLimit uint64 - GenesisAmount uint64 - HalfLifeInDays float64 - IncomeBase uint64 - IncomeLimit uint64 - MaxOutboundsCount int - MinimalTransactionFee uint64 - SmallestUnitsPerCoin uint64 - SynchronizationIntervalInSeconds int - ValidationIntervalInSeconds int64 - ValidationTimeoutInSeconds int64 - VerificationsCountPerValidation int64 -} - -type Settings struct { - bytes []byte - blocksCountLimit uint64 - genesisAmount uint64 - halfLifeInNanoseconds float64 - incomeBase uint64 - incomeLimit uint64 - maxOutboundsCount int - minimalTransactionFee uint64 - smallestUnitsPerCoin uint64 - synchronizationTimer time.Duration - validationTimestamp int64 - validationTimer time.Duration - validationTimeout time.Duration - verificationsCountPerValidation int64 -} - -func NewSettings(path string) (*Settings, error) { - jsonFile, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("unable to open file: %w", err) - } - var settings *Settings - bytes, err := io.ReadAll(jsonFile) - if err != nil { - return nil, fmt.Errorf("unable to read file: %w", err) - } - if err = jsonFile.Close(); err != nil { - return nil, fmt.Errorf("unable to close file: %w", err) - } - if err = json.Unmarshal(bytes, &settings); err != nil { - return nil, fmt.Errorf("unable to unmarshal: %w", err) - } - return settings, nil -} - -func (settings *Settings) UnmarshalJSON(data []byte) error { - var dto *settingsDto - err := json.Unmarshal(data, &dto) - if err != nil { - return err - } - settings.bytes = data - settings.blocksCountLimit = dto.BlocksCountLimit - settings.genesisAmount = dto.GenesisAmount - hoursByDay := 24. - settings.halfLifeInNanoseconds = dto.HalfLifeInDays * hoursByDay * float64(time.Hour.Nanoseconds()) - settings.incomeBase = dto.IncomeBase - settings.incomeLimit = dto.IncomeLimit - settings.maxOutboundsCount = dto.MaxOutboundsCount - settings.minimalTransactionFee = dto.MinimalTransactionFee - settings.smallestUnitsPerCoin = dto.SmallestUnitsPerCoin - settings.synchronizationTimer = time.Duration(dto.SynchronizationIntervalInSeconds) * time.Second - settings.validationTimestamp = dto.ValidationIntervalInSeconds * time.Second.Nanoseconds() - settings.validationTimer = time.Duration(dto.ValidationIntervalInSeconds) * time.Second - settings.validationTimeout = time.Duration(dto.ValidationTimeoutInSeconds) * time.Second - settings.verificationsCountPerValidation = dto.VerificationsCountPerValidation - return nil -} - -func (settings *Settings) Bytes() []byte { - return settings.bytes -} - -func (settings *Settings) BlocksCountLimit() uint64 { - return settings.blocksCountLimit -} - -func (settings *Settings) GenesisAmount() uint64 { - return settings.genesisAmount -} - -func (settings *Settings) HalfLifeInNanoseconds() float64 { - return settings.halfLifeInNanoseconds -} - -func (settings *Settings) IncomeBase() uint64 { - return settings.incomeBase -} - -func (settings *Settings) IncomeLimit() uint64 { - return settings.incomeLimit -} - -func (settings *Settings) MaxOutboundsCount() int { - return settings.maxOutboundsCount -} - -func (settings *Settings) MinimalTransactionFee() uint64 { - return settings.minimalTransactionFee -} - -func (settings *Settings) SmallestUnitsPerCoin() uint64 { - return settings.smallestUnitsPerCoin -} - -func (settings *Settings) SynchronizationTimer() time.Duration { - return settings.synchronizationTimer -} - -func (settings *Settings) ValidationTimer() time.Duration { - return settings.validationTimer -} - -func (settings *Settings) ValidationTimestamp() int64 { - return settings.validationTimestamp -} - -func (settings *Settings) ValidationTimeout() time.Duration { - return settings.validationTimeout -} - -func (settings *Settings) VerificationsCountPerValidation() int64 { - return settings.verificationsCountPerValidation -} diff --git a/src/node/clock/engine.go b/src/node/clock/engine.go deleted file mode 100644 index a36aa69c..00000000 --- a/src/node/clock/engine.go +++ /dev/null @@ -1,7 +0,0 @@ -package clock - -type Engine interface { - Start() - Stop() - Do() -} diff --git a/src/node/clock/watch.go b/src/node/clock/watch.go deleted file mode 100644 index 3c4d2e6e..00000000 --- a/src/node/clock/watch.go +++ /dev/null @@ -1,7 +0,0 @@ -package clock - -import "time" - -type Watch interface { - Now() time.Time -} diff --git a/src/node/main.go b/src/node/main.go deleted file mode 100644 index aa5722e8..00000000 --- a/src/node/main.go +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "github.com/my-cloud/ruthenium/src/config" - "github.com/my-cloud/ruthenium/src/file" - "strconv" - - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/src/environment" - "github.com/my-cloud/ruthenium/src/log/console" - "github.com/my-cloud/ruthenium/src/node/clock/tick" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "github.com/my-cloud/ruthenium/src/node/network/p2p/gp2p" - "github.com/my-cloud/ruthenium/src/node/network/p2p/net" - "github.com/my-cloud/ruthenium/src/node/protocol/poh" - "github.com/my-cloud/ruthenium/src/node/protocol/validation" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" -) - -func main() { - mnemonic := flag.String("mnemonic", environment.NewVariable("MNEMONIC").GetStringValue(""), "The mnemonic (required if the private key is not provided)") - derivationPath := flag.String("derivation-path", environment.NewVariable("DERIVATION_PATH").GetStringValue("m/44'/60'/0'/0/0"), "The derivation path (unused if the mnemonic is omitted)") - password := flag.String("password", environment.NewVariable("PASSWORD").GetStringValue(""), "The mnemonic password (unused if the mnemonic is omitted)") - privateKeyString := flag.String("private-key", environment.NewVariable("PRIVATE_KEY").GetStringValue(""), "The private key (required if the mnemonic is not provided, unused if the mnemonic is provided)") - infuraKey := flag.String("infura-key", environment.NewVariable("INFURA_KEY").GetStringValue(""), "The infura key (required to check the proof of humanity)") - ip := flag.String("ip", environment.NewVariable("IP").GetStringValue(""), "The node IP or DNS address (detected if not provided)") - port := flag.Int("port", environment.NewVariable("PORT").GetIntValue(10600), "The TCP port number of the host node") - settingsPath := flag.String("settings-path", environment.NewVariable("SETTINGS_PATH").GetStringValue("config/settings.json"), "The settings file path") - seedsPath := flag.String("seeds-path", environment.NewVariable("SEEDS_PATH").GetStringValue("config/seeds.json"), "The seeds file path") - logLevel := flag.String("log-level", environment.NewVariable("LOG_LEVEL").GetStringValue("info"), "The log level (possible values: 'debug', 'info', 'warn', 'error', 'fatal')") - - flag.Parse() - logger := console.NewLogger(console.ParseLevel(*logLevel)) - address := decodeAddress(mnemonic, derivationPath, password, privateKeyString, logger) - host := createHost(settingsPath, infuraKey, seedsPath, ip, port, address, logger) - logger.Info(fmt.Sprintf("host node starting for address: %s", address)) - err := host.Run() - if err != nil { - logger.Fatal(fmt.Errorf("failed to run host: %w", err).Error()) - } -} - -func decodeAddress(mnemonic *string, derivationPath *string, password *string, privateKeyString *string, logger *console.Logger) string { - var privateKey *encryption.PrivateKey - var err error - if *mnemonic != "" { - privateKey, err = encryption.NewPrivateKeyFromMnemonic(*mnemonic, *derivationPath, *password) - } else if *privateKeyString != "" { - privateKey, err = encryption.NewPrivateKeyFromHex(*privateKeyString) - } else { - logger.Fatal(fmt.Errorf("nor the mnemonic neither the private key have been provided").Error()) - } - if err != nil { - logger.Fatal(fmt.Errorf("failed to create private key: %w", err).Error()) - } - publicKey := encryption.NewPublicKey(privateKey) - return publicKey.Address() -} - -func createHost(settingsPath *string, infuraKey *string, seedsPath *string, ip *string, port *int, address string, logger *console.Logger) *p2p.Host { - settings, err := config.NewSettings(*settingsPath) - if err != nil { - logger.Fatal(fmt.Errorf("unable to parse settings: %w", err).Error()) - } - registry := poh.NewRegistry(*infuraKey, logger) - watch := tick.NewWatch() - synchronizer := createSynchronizer(*seedsPath, *ip, *port, settings, watch, logger) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - transactionsPool := validation.NewTransactionsPool(blockchain, settings, synchronizer, address, logger) - synchronizationEngine := tick.NewEngine(synchronizer.Synchronize, watch, settings.SynchronizationTimer(), 1, 0) - validationEngine := tick.NewEngine(transactionsPool.Validate, watch, settings.ValidationTimer(), 1, 0) - verificationEngine := tick.NewEngine(blockchain.Update, watch, settings.ValidationTimer(), settings.VerificationsCountPerValidation(), 1) - handler := gp2p.NewHandler(blockchain, settings.Bytes(), synchronizer, transactionsPool, watch, logger) - serverFactory := gp2p.NewServerFactory(handler, settings) - server, err := serverFactory.CreateServer(*port) - if err != nil { - logger.Fatal(fmt.Errorf("failed to create server: %w", err).Error()) - } - return p2p.NewHost(server, synchronizationEngine, validationEngine, verificationEngine, logger) -} - -func createSynchronizer(seedsPath string, hostIp string, port int, settings *config.Settings, watch *tick.Watch, logger *console.Logger) *p2p.Synchronizer { - var seedsStringTargets []string - parser := file.NewJsonParser() - err := parser.Parse(seedsPath, &seedsStringTargets) - if err != nil { - logger.Fatal(fmt.Errorf("unable to parse seeds: %w", err).Error()) - } - scoresBySeedTarget := map[string]int{} - for _, seedStringTarget := range seedsStringTargets { - scoresBySeedTarget[seedStringTarget] = 0 - } - ipFinder := net.NewIpFinder(logger) - if hostIp == "" { - hostIp, err = ipFinder.FindHostPublicIp() - if err != nil { - logger.Fatal(fmt.Errorf("failed to find the public IP: %w", err).Error()) - } - } - clientFactory := gp2p.NewClientFactory(ipFinder, settings.ValidationTimeout()) - return p2p.NewSynchronizer(clientFactory, hostIp, strconv.Itoa(port), settings.MaxOutboundsCount(), scoresBySeedTarget, watch) -} diff --git a/src/node/network/p2p/client.go b/src/node/network/p2p/client.go deleted file mode 100644 index b544deeb..00000000 --- a/src/node/network/p2p/client.go +++ /dev/null @@ -1,5 +0,0 @@ -package p2p - -type Client interface { - Send(topic string, req []byte) (res []byte, err error) -} diff --git a/src/node/network/p2p/client_factory.go b/src/node/network/p2p/client_factory.go deleted file mode 100644 index 4361cdf4..00000000 --- a/src/node/network/p2p/client_factory.go +++ /dev/null @@ -1,5 +0,0 @@ -package p2p - -type ClientFactory interface { - CreateClient(ip string, port string) (Client, error) -} diff --git a/src/node/network/p2p/gp2p/client.go b/src/node/network/p2p/gp2p/client.go deleted file mode 100644 index a27f95c6..00000000 --- a/src/node/network/p2p/gp2p/client.go +++ /dev/null @@ -1,33 +0,0 @@ -package gp2p - -import ( - gp2p "github.com/leprosus/golang-p2p" - "github.com/my-cloud/ruthenium/src/log" - "time" -) - -type Client struct { - *gp2p.Client -} - -func NewClient(ip string, port string, connectionTimeout time.Duration, logger log.Logger) (*Client, error) { - tcp := gp2p.NewTCP(ip, port) - client, err := gp2p.NewClient(tcp) - if err != nil { - return nil, err - } - settings := gp2p.NewClientSettings() - settings.SetRetry(1, time.Nanosecond) - settings.SetConnTimeout(connectionTimeout) - client.SetSettings(settings) - client.SetLogger(logger) - return &Client{client}, err -} - -func (client *Client) Send(topic string, req []byte) (res []byte, err error) { - data, err := client.Client.Send(topic, gp2p.Data{Bytes: req}) - if err != nil { - return []byte{}, err - } - return data.Bytes, err -} diff --git a/src/node/network/p2p/gp2p/client_factory.go b/src/node/network/p2p/gp2p/client_factory.go deleted file mode 100644 index c0e7b31b..00000000 --- a/src/node/network/p2p/gp2p/client_factory.go +++ /dev/null @@ -1,30 +0,0 @@ -package gp2p - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/log/console" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "time" -) - -type ClientFactory struct { - ipFinder network.IpFinder - connectionTimeout time.Duration -} - -func NewClientFactory(ipFinder network.IpFinder, connectionTimeout time.Duration) *ClientFactory { - return &ClientFactory{ipFinder, connectionTimeout} -} - -func (factory *ClientFactory) CreateClient(ip string, port string) (p2p.Client, error) { - lookedUpIp, err := factory.ipFinder.LookupIP(ip) - if err != nil { - return nil, fmt.Errorf("failed to look up IP on addresse %s: %w", ip, err) - } - client, err := NewClient(lookedUpIp, port, factory.connectionTimeout, console.NewLogger(console.Fatal)) - if err != nil { - return nil, fmt.Errorf("failed to instantiate client for address %s: %w", ip, err) - } - return client, err -} diff --git a/src/node/network/p2p/gp2p/handler.go b/src/node/network/p2p/gp2p/handler.go deleted file mode 100644 index b8d67092..00000000 --- a/src/node/network/p2p/gp2p/handler.go +++ /dev/null @@ -1,99 +0,0 @@ -package gp2p - -import ( - "context" - "encoding/json" - gp2p "github.com/leprosus/golang-p2p" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol" -) - -const BadRequest = "bad request" - -type Handler struct { - blockchain protocol.Blockchain - settings []byte - synchronizer network.Synchronizer - transactionsPool protocol.TransactionsPool - watch clock.Watch - logger log.Logger -} - -func NewHandler(blockchain protocol.Blockchain, - settings []byte, - synchronizer network.Synchronizer, - transactionsPool protocol.TransactionsPool, - watch clock.Watch, - logger log.Logger) *Handler { - return &Handler{blockchain, settings, synchronizer, transactionsPool, watch, logger} -} - -func (handler *Handler) HandleBlocksRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { - var startingBlockHeight uint64 - res := gp2p.Data{} - data := req.GetBytes() - if err := json.Unmarshal(data, &startingBlockHeight); err != nil { - handler.logger.Debug(BadRequest) - return res, err - } - blocks := handler.blockchain.Blocks(startingBlockHeight) - res.SetBytes(blocks) - return res, nil -} - -func (handler *Handler) HandleFirstBlockTimestampRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { - res := gp2p.Data{} - timestamp := handler.blockchain.FirstBlockTimestamp() - timestampBytes, err := json.Marshal(timestamp) - if err != nil { - return res, err - } - res.SetBytes(timestampBytes) - return res, nil -} - -func (handler *Handler) HandleSettingsRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { - res := gp2p.Data{} - res.SetBytes(handler.settings) - return res, nil -} - -func (handler *Handler) HandleTargetsRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { - res := gp2p.Data{} - var targets []string - data := req.GetBytes() - if err := json.Unmarshal(data, &targets); err != nil { - handler.logger.Debug(BadRequest) - return res, err - } - go handler.synchronizer.AddTargets(targets) - return res, nil -} - -func (handler *Handler) HandleTransactionRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { - data := req.GetBytes() - go handler.transactionsPool.AddTransaction(data, handler.synchronizer.HostTarget()) - return gp2p.Data{}, nil -} - -func (handler *Handler) HandleTransactionsRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { - res := gp2p.Data{} - transactions := handler.transactionsPool.Transactions() - res.SetBytes(transactions) - return res, nil -} - -func (handler *Handler) HandleUtxosRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { - res := gp2p.Data{} - var address string - data := req.GetBytes() - if err := json.Unmarshal(data, &address); err != nil { - handler.logger.Debug(BadRequest) - return res, err - } - utxosByAddress := handler.blockchain.Utxos(address) - res.SetBytes(utxosByAddress) - return res, nil -} diff --git a/src/node/network/p2p/gp2p/server.go b/src/node/network/p2p/gp2p/server.go deleted file mode 100644 index 1014eae7..00000000 --- a/src/node/network/p2p/gp2p/server.go +++ /dev/null @@ -1,56 +0,0 @@ -package gp2p - -import ( - "fmt" - gp2p "github.com/leprosus/golang-p2p" - "github.com/my-cloud/ruthenium/src/log/console" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "strconv" - "time" -) - -type Server struct { - *gp2p.Server - handler p2p.Handler -} - -func NewServer(port int, handler p2p.Handler, timeout time.Duration) (*Server, error) { - tcp := gp2p.NewTCP("0.0.0.0", strconv.Itoa(port)) - server, err := gp2p.NewServer(tcp) - if err != nil { - return nil, fmt.Errorf("failed to instantiate server on port %d: %w", port, err) - } - server.SetLogger(console.NewLogger(console.Fatal)) - settings := gp2p.NewServerSettings() - settings.SetConnTimeout(timeout) - server.SetSettings(settings) - return &Server{server, handler}, err -} - -func (server *Server) SetHandleBlocksRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleBlocksRequest) -} - -func (server *Server) SetHandleFirstBlockTimestampRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleFirstBlockTimestampRequest) -} - -func (server *Server) SetHandleSettingsRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleSettingsRequest) -} - -func (server *Server) SetHandleTargetsRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleTargetsRequest) -} - -func (server *Server) SetHandleTransactionRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleTransactionRequest) -} - -func (server *Server) SetHandleTransactionsRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleTransactionsRequest) -} - -func (server *Server) SetHandleUtxosRequest(endpoint string) { - server.SetHandle(endpoint, server.handler.HandleUtxosRequest) -} diff --git a/src/node/network/p2p/gp2p/server_factory.go b/src/node/network/p2p/gp2p/server_factory.go deleted file mode 100644 index 5e952cf2..00000000 --- a/src/node/network/p2p/gp2p/server_factory.go +++ /dev/null @@ -1,22 +0,0 @@ -package gp2p - -import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" -) - -type ServerFactory struct { - handler p2p.Handler - settings p2p.Settings -} - -func NewServerFactory(handler p2p.Handler, settings p2p.Settings) *ServerFactory { - return &ServerFactory{handler, settings} -} - -func (factory *ServerFactory) CreateServer(port int) (p2p.Server, error) { - server, err := NewServer(port, factory.handler, factory.settings.ValidationTimeout()) - if err != nil { - return nil, err - } - return server, nil -} diff --git a/src/node/network/p2p/handler.go b/src/node/network/p2p/handler.go deleted file mode 100644 index 68726e1c..00000000 --- a/src/node/network/p2p/handler.go +++ /dev/null @@ -1,16 +0,0 @@ -package p2p - -import ( - "context" - gp2p "github.com/leprosus/golang-p2p" -) - -type Handler interface { - HandleBlocksRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleFirstBlockTimestampRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleSettingsRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleTargetsRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleTransactionRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleTransactionsRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) - HandleUtxosRequest(_ context.Context, req gp2p.Data) (res gp2p.Data, err error) -} diff --git a/src/node/network/p2p/host.go b/src/node/network/p2p/host.go deleted file mode 100644 index 84725524..00000000 --- a/src/node/network/p2p/host.go +++ /dev/null @@ -1,46 +0,0 @@ -package p2p - -import ( - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" -) - -const ( - blocksEndpoint = "blocks" - firstBlockTimestampEndpoint = "first-block-timestamp" - settingsEndpoint = "settings" - targetsEndpoint = "targets" - transactionEndpoint = "transaction" - transactionsEndpoint = "transactions" - utxosEndpoint = "utxos" -) - -type Host struct { - server Server - synchronizationEngine clock.Engine - validationEngine clock.Engine - verificationEngine clock.Engine - logger log.Logger -} - -func NewHost(server Server, synchronizationEngine clock.Engine, validationEngine clock.Engine, verificationEngine clock.Engine, logger log.Logger) *Host { - return &Host{server, synchronizationEngine, validationEngine, verificationEngine, logger} -} - -func (host *Host) Run() error { - go host.synchronizationEngine.Start() - go host.validationEngine.Start() - go host.verificationEngine.Start() - host.setServerHandles() - return host.server.Serve() -} - -func (host *Host) setServerHandles() { - host.server.SetHandleBlocksRequest(blocksEndpoint) - host.server.SetHandleFirstBlockTimestampRequest(firstBlockTimestampEndpoint) - host.server.SetHandleSettingsRequest(settingsEndpoint) - host.server.SetHandleTargetsRequest(targetsEndpoint) - host.server.SetHandleTransactionRequest(transactionEndpoint) - host.server.SetHandleTransactionsRequest(transactionsEndpoint) - host.server.SetHandleUtxosRequest(utxosEndpoint) -} diff --git a/src/node/network/p2p/neighbor.go b/src/node/network/p2p/neighbor.go deleted file mode 100644 index c0c43766..00000000 --- a/src/node/network/p2p/neighbor.go +++ /dev/null @@ -1,70 +0,0 @@ -package p2p - -import ( - "encoding/json" - "fmt" -) - -type Neighbor struct { - target *Target - client Client -} - -func NewNeighbor(target *Target, clientFactory ClientFactory) (*Neighbor, error) { - client, err := clientFactory.CreateClient(target.Ip(), target.Port()) - if err != nil { - return nil, fmt.Errorf("failed to create client reaching %s: %w", target.Value(), err) - } - return &Neighbor{target, client}, nil -} - -func (neighbor *Neighbor) Target() string { - return neighbor.target.Value() -} - -func (neighbor *Neighbor) GetBlocks(startingBlockHeight uint64) ([]byte, error) { - return neighbor.sendRequest(blocksEndpoint, startingBlockHeight) -} - -func (neighbor *Neighbor) GetFirstBlockTimestamp() (int64, error) { - res, err := neighbor.client.Send(firstBlockTimestampEndpoint, []byte{}) - var timestamp int64 - if err != nil { - return timestamp, err - } - err = json.Unmarshal(res, ×tamp) - if err != nil { - return timestamp, err - } - return timestamp, err -} - -func (neighbor *Neighbor) GetSettings() ([]byte, error) { - return neighbor.sendRequest(settingsEndpoint, []byte{}) -} - -func (neighbor *Neighbor) SendTargets(targets []string) error { - _, err := neighbor.sendRequest(targetsEndpoint, targets) - return err -} - -func (neighbor *Neighbor) AddTransaction(transaction []byte) error { - _, err := neighbor.client.Send(transactionEndpoint, transaction) - return err -} - -func (neighbor *Neighbor) GetTransactions() ([]byte, error) { - return neighbor.client.Send(transactionsEndpoint, []byte{}) -} - -func (neighbor *Neighbor) GetUtxos(address string) ([]byte, error) { - return neighbor.sendRequest(utxosEndpoint, address) -} - -func (neighbor *Neighbor) sendRequest(topic string, request interface{}) ([]byte, error) { - bytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - return neighbor.client.Send(topic, bytes) -} diff --git a/src/node/network/p2p/net/ip_finder.go b/src/node/network/p2p/net/ip_finder.go deleted file mode 100644 index 4d49a349..00000000 --- a/src/node/network/p2p/net/ip_finder.go +++ /dev/null @@ -1,46 +0,0 @@ -package net - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "io" - "net" - "net/http" -) - -type IpFinder struct { - logger log.Logger -} - -func NewIpFinder(logger log.Logger) *IpFinder { - return &IpFinder{logger} -} - -func (finder *IpFinder) LookupIP(ip string) (string, error) { - ips, err := net.LookupIP(ip) - if err != nil { - return "", fmt.Errorf("DNS discovery failed on addresse %s: %w", ip, err) - } - ipsCount := len(ips) - if ipsCount != 1 { - return "", fmt.Errorf("DNS discovery did not find a single address (%d addresses found) for the given IP %s", ipsCount, ip) - } - return ips[0].String(), nil -} - -func (finder *IpFinder) FindHostPublicIp() (string, error) { - resp, err := http.Get("https://ifconfig.me") - if err != nil { - return "", err - } - defer func() { - if bodyCloseError := resp.Body.Close(); bodyCloseError != nil { - finder.logger.Error(fmt.Errorf("failed to close public IP request body: %w", bodyCloseError).Error()) - } - }() - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", err - } - return string(body), nil -} diff --git a/src/node/network/p2p/settings.go b/src/node/network/p2p/settings.go deleted file mode 100644 index 8ae17f5f..00000000 --- a/src/node/network/p2p/settings.go +++ /dev/null @@ -1,7 +0,0 @@ -package p2p - -import "time" - -type Settings interface { - ValidationTimeout() time.Duration -} diff --git a/src/node/network/p2p/synchronizer.go b/src/node/network/p2p/synchronizer.go deleted file mode 100644 index 8dfe5fe6..00000000 --- a/src/node/network/p2p/synchronizer.go +++ /dev/null @@ -1,140 +0,0 @@ -package p2p - -import ( - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/network" - "math/rand" - "sort" - "sync" - "time" -) - -type Synchronizer struct { - clientFactory ClientFactory - hostTarget *Target - maxOutboundsCount int - neighbors []network.Neighbor - neighborsMutex sync.RWMutex - scoresBySeedTargetValue map[string]int - scoresByTargetValue map[string]int - scoresByTargetValueMutex sync.RWMutex - watch clock.Watch -} - -func NewSynchronizer(clientFactory ClientFactory, hostIp string, hostPort string, maxOutboundsCount int, scoresBySeedTargetValue map[string]int, watch clock.Watch) *Synchronizer { - synchronizer := new(Synchronizer) - synchronizer.clientFactory = clientFactory - synchronizer.hostTarget = NewTarget(hostIp, hostPort) - synchronizer.maxOutboundsCount = maxOutboundsCount - synchronizer.scoresBySeedTargetValue = scoresBySeedTargetValue - synchronizer.scoresByTargetValue = map[string]int{} - synchronizer.watch = watch - return synchronizer -} - -func (synchronizer *Synchronizer) AddTargets(targetValues []string) { - synchronizer.scoresByTargetValueMutex.Lock() - defer synchronizer.scoresByTargetValueMutex.Unlock() - for _, targetValue := range targetValues { - _, isTargetAlreadyKnown := synchronizer.scoresByTargetValue[targetValue] - target, err := NewTargetFromValue(targetValue) - if err != nil { - continue - } - isTargetOnSameNetwork := synchronizer.hostTarget.IsSameNetworkId(target) - if !isTargetAlreadyKnown && isTargetOnSameNetwork { - synchronizer.scoresByTargetValue[targetValue] = 0 - } - } -} - -func (synchronizer *Synchronizer) HostTarget() string { - return synchronizer.hostTarget.Value() -} - -func (synchronizer *Synchronizer) Incentive(targetValue string) { - synchronizer.scoresByTargetValueMutex.Lock() - defer synchronizer.scoresByTargetValueMutex.Unlock() - synchronizer.scoresByTargetValue[targetValue] += 1 -} - -func (synchronizer *Synchronizer) Neighbors() []network.Neighbor { - return synchronizer.neighbors -} - -func (synchronizer *Synchronizer) Synchronize(int64) { - synchronizer.scoresByTargetValueMutex.Lock() - var scoresByTargetValue map[string]int - if len(synchronizer.scoresByTargetValue) == 0 { - scoresByTargetValue = synchronizer.scoresBySeedTargetValue - } else { - scoresByTargetValue = synchronizer.scoresByTargetValue - } - synchronizer.scoresByTargetValue = map[string]int{} - synchronizer.scoresByTargetValueMutex.Unlock() - neighborsByScore := map[int][]network.Neighbor{} - var targetValues []string - hostTargetValue := synchronizer.hostTarget.Value() - targetValues = append(targetValues, hostTargetValue) - for targetValue, score := range scoresByTargetValue { - if targetValue != hostTargetValue { - neighborTarget, err := NewTargetFromValue(targetValue) - if err != nil { - continue - } - neighbor, err := NewNeighbor(neighborTarget, synchronizer.clientFactory) - if err != nil { - continue - } - neighborsByScore[score] = append(neighborsByScore[score], neighbor) - targetValues = append(targetValues, targetValue) - } - } - outbounds := synchronizer.selectOutbounds(neighborsByScore, len(scoresByTargetValue)) - synchronizer.neighborsMutex.Lock() - synchronizer.neighbors = outbounds - synchronizer.neighborsMutex.Unlock() - for _, neighbor := range outbounds { - var neighborTargetValues []string - for _, targetValue := range targetValues { - neighborTargetValue := neighbor.Target() - if neighborTargetValue != targetValue { - neighborTargetValues = append(neighborTargetValues, targetValue) - } - } - go func(neighbor network.Neighbor) { - _ = neighbor.SendTargets(neighborTargetValues) - }(neighbor) - } -} - -func (synchronizer *Synchronizer) selectOutbounds(neighborsByScore map[int][]network.Neighbor, targetsCount int) []network.Neighbor { - var keys []int - for k := range neighborsByScore { - keys = append(keys, k) - } - sort.Ints(keys) - outboundsCount := min(targetsCount, synchronizer.maxOutboundsCount) - var outbounds []network.Neighbor - for i := len(keys) - 1; i >= 0; i-- { - if len(outbounds)+len(neighborsByScore[keys[i]]) >= outboundsCount { - temp := neighborsByScore[keys[i]] - rand.Seed(time.Now().UnixNano()) - rand.Shuffle(len(temp), func(i, j int) { temp[i], temp[j] = temp[j], temp[i] }) - outbounds = append(outbounds, temp[:outboundsCount-len(outbounds)]...) - break - } - outbounds = append(outbounds, neighborsByScore[keys[i]]...) - } - return outbounds -} - -func min(first, second int) int { - var result int - if first < second { - result = first - } else { - result = second - } - return result -} diff --git a/src/node/protocol/blockchain.go b/src/node/protocol/blockchain.go deleted file mode 100644 index 5542aca0..00000000 --- a/src/node/protocol/blockchain.go +++ /dev/null @@ -1,11 +0,0 @@ -package protocol - -type Blockchain interface { - AddBlock(timestamp int64, transactionsBytes []byte, newRegisteredAddresses []string) error - Blocks(startingBlockHeight uint64) []byte - Copy() Blockchain // TODO remove - FirstBlockTimestamp() int64 - LastBlockTimestamp() int64 - Utxos(address string) []byte - Utxo(input InputInfo) (Utxo, error) -} diff --git a/src/node/protocol/input.go b/src/node/protocol/input.go deleted file mode 100644 index 89d71c53..00000000 --- a/src/node/protocol/input.go +++ /dev/null @@ -1,6 +0,0 @@ -package protocol - -type InputInfo interface { - TransactionId() string - OutputIndex() uint16 -} diff --git a/src/node/protocol/registry.go b/src/node/protocol/registry.go deleted file mode 100644 index 6e79916a..00000000 --- a/src/node/protocol/registry.go +++ /dev/null @@ -1,5 +0,0 @@ -package protocol - -type Registry interface { - IsRegistered(address string) (bool, error) -} diff --git a/src/node/protocol/transactions_pool.go b/src/node/protocol/transactions_pool.go deleted file mode 100644 index 735e05c7..00000000 --- a/src/node/protocol/transactions_pool.go +++ /dev/null @@ -1,6 +0,0 @@ -package protocol - -type TransactionsPool interface { - AddTransaction(transactionRequestBytes []byte, hostTarget string) - Transactions() []byte -} diff --git a/src/node/protocol/utxo.go b/src/node/protocol/utxo.go deleted file mode 100644 index dbcf2ee1..00000000 --- a/src/node/protocol/utxo.go +++ /dev/null @@ -1,6 +0,0 @@ -package protocol - -type Utxo interface { - Address() string - Value(currentTimestamp int64, halfLifeInNanoseconds float64, incomeBase uint64, incomeLimit uint64) uint64 -} diff --git a/src/node/protocol/utxo_finder.go b/src/node/protocol/utxo_finder.go deleted file mode 100644 index e20e2b08..00000000 --- a/src/node/protocol/utxo_finder.go +++ /dev/null @@ -1,3 +0,0 @@ -package protocol - -type UtxoFinder func(input InputInfo) (Utxo, error) diff --git a/src/node/protocol/validation/transactions_pool.go b/src/node/protocol/validation/transactions_pool.go deleted file mode 100644 index dd896c83..00000000 --- a/src/node/protocol/validation/transactions_pool.go +++ /dev/null @@ -1,251 +0,0 @@ -package validation - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/clock/tick" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "math/rand" - "sync" - "time" -) - -type TransactionsPool struct { - transactions []*verification.Transaction - mutex sync.RWMutex - - blockchain protocol.Blockchain - settings protocol.Settings - synchronizer network.Synchronizer - validatorAddress string - - watch clock.Watch - logger log.Logger -} - -func NewTransactionsPool(blockchain protocol.Blockchain, settings protocol.Settings, synchronizer network.Synchronizer, validatorAddress string, logger log.Logger) *TransactionsPool { - pool := new(TransactionsPool) - pool.blockchain = blockchain - pool.settings = settings - pool.synchronizer = synchronizer - pool.validatorAddress = validatorAddress - pool.watch = tick.NewWatch() - pool.logger = logger - return pool -} - -func (pool *TransactionsPool) AddTransaction(transactionRequestBytes []byte, hostTarget string) { - var transactionRequest TransactionRequest - if err := json.Unmarshal(transactionRequestBytes, &transactionRequest); err != nil { - pool.logger.Debug(fmt.Errorf("failed to unmarshal transaction: %w", err).Error()) - return - } - transaction := transactionRequest.Transaction() - err := pool.addTransaction(transaction) - if err != nil { - pool.logger.Debug(fmt.Errorf("failed to add transaction: %w", err).Error()) - return - } - pool.synchronizer.Incentive(transactionRequest.TransactionBroadcasterTarget()) - newTransactionRequest := NewTransactionRequest(transaction, hostTarget) - marshaledTransactionRequest, err := json.Marshal(newTransactionRequest) - if err != nil { - pool.logger.Debug(fmt.Errorf("failed to marshal transaction request: %w", err).Error()) - return - } - neighbors := pool.synchronizer.Neighbors() - for _, neighbor := range neighbors { - go func(neighbor network.Neighbor) { - _ = neighbor.AddTransaction(marshaledTransactionRequest) - }(neighbor) - } -} - -func (pool *TransactionsPool) Transactions() []byte { - pool.mutex.RLock() - defer pool.mutex.RUnlock() - transactionsBytes, err := json.Marshal(pool.transactions) - if err != nil { - pool.logger.Error(err.Error()) - return nil - } - return transactionsBytes -} - -func (pool *TransactionsPool) Validate(timestamp int64) { - blockchainCopy := pool.blockchain.Copy() - lastBlockTimestamp := blockchainCopy.LastBlockTimestamp() - nextBlockTimestamp := lastBlockTimestamp + pool.settings.ValidationTimestamp() - var reward uint64 - var newAddresses []string - var isYielding bool - if lastBlockTimestamp == 0 { - reward = pool.settings.GenesisAmount() - newAddresses = []string{pool.validatorAddress} - isYielding = true - } else if lastBlockTimestamp == timestamp { - pool.logger.Error("unable to create block, a block with the same timestamp is already in the blockchain") - return - } else if timestamp > nextBlockTimestamp { - pool.logger.Error("unable to create block, a block is missing in the blockchain") - return - } - err := blockchainCopy.AddBlock(timestamp, nil, nil) - if err != nil { - pool.logger.Error("failed to add temporary block") - } - pool.mutex.Lock() - defer pool.mutex.Unlock() - isAlreadySpentByOutputIdByTransactionIndex := make(map[string]map[uint16]bool) - transactions := pool.transactions - rand.Seed(pool.watch.Now().UnixNano()) - rand.Shuffle(len(transactions), func(i, j int) { - transactions[i], transactions[j] = transactions[j], transactions[i] - }) - var rejectedTransactions []*verification.Transaction - for _, transaction := range transactions { - if timestamp < transaction.Timestamp() { - pool.logger.Warn(fmt.Sprintf("transaction removed from the transactions pool, the transaction timestamp is too far in the future, transaction: %v", transaction)) - rejectedTransactions = append(rejectedTransactions, transaction) - continue - } - if transaction.Timestamp() < lastBlockTimestamp { - pool.logger.Warn(fmt.Sprintf("transaction removed from the transactions pool, the transaction timestamp is too old, transaction: %v", transaction)) - rejectedTransactions = append(rejectedTransactions, transaction) - continue - } - if err = transaction.VerifySignatures(blockchainCopy.Utxo); err != nil { - pool.logger.Warn(fmt.Errorf("failed to verify transaction: %w", err).Error()) - rejectedTransactions = append(rejectedTransactions, transaction) - continue - } - fee, err := transaction.Fee(pool.settings, timestamp, blockchainCopy.Utxo) - if err != nil { - pool.logger.Warn(fmt.Errorf("transaction removed from the transactions pool, transaction: %v\n %w", transaction, err).Error()) - rejectedTransactions = append(rejectedTransactions, transaction) - continue - } - var isAlreadySpent bool - for _, input := range transaction.Inputs() { - if isAlreadySpentByOutputIndex, ok := isAlreadySpentByOutputIdByTransactionIndex[input.TransactionId()]; ok { - if _, isAlreadySpent = isAlreadySpentByOutputIndex[input.OutputIndex()]; isAlreadySpent { - pool.logger.Warn(fmt.Sprintf("transaction removed from the transactions pool, an input has already been spent, transaction: %v", transaction)) - rejectedTransactions = append(rejectedTransactions, transaction) - break - } - } else { - isAlreadySpentByOutputIdByTransactionIndex[input.TransactionId()] = make(map[uint16]bool) - } - isAlreadySpentByOutputIdByTransactionIndex[input.TransactionId()][input.OutputIndex()] = true - } - if !isAlreadySpent { - reward += fee - } - } - for _, transaction := range rejectedTransactions { - transactions = removeTransaction(transactions, transaction) - } - for _, transaction := range transactions { - for _, output := range transaction.Outputs() { - if output.IsYielding() { - newAddresses = append(newAddresses, output.Address()) - } - } - } - rewardTransaction, err := verification.NewRewardTransaction(pool.validatorAddress, isYielding, timestamp, reward) - if err != nil { - pool.logger.Error(fmt.Errorf("unable to create block, failed to create reward transaction: %w", err).Error()) - return - } - transactions = append(transactions, rewardTransaction) - secondBlockchainCopy := pool.blockchain.Copy() - transactionsBytes, err := json.Marshal(transactions) - if err != nil { - pool.logger.Error(fmt.Errorf("failed to marshal transactions: %w", err).Error()) - } - err = secondBlockchainCopy.AddBlock(timestamp, transactionsBytes, nil) - if err != nil { - pool.logger.Error(fmt.Errorf("next block creation would fail: %w", err).Error()) - } - err = secondBlockchainCopy.AddBlock(nextBlockTimestamp, nil, nil) - if err != nil { - pool.logger.Error(fmt.Errorf("later block creation would fail: %w", err).Error()) - return - } - err = pool.blockchain.AddBlock(timestamp, transactionsBytes, newAddresses) - if err != nil { - pool.logger.Error(fmt.Errorf("unable to create block: %w", err).Error()) - return - } - pool.clear() - pool.logger.Debug(fmt.Sprintf("reward: %d", reward)) -} - -func (pool *TransactionsPool) addTransaction(transaction *verification.Transaction) error { - blockchainCopy := pool.blockchain.Copy() - lastBlockTimestamp := blockchainCopy.LastBlockTimestamp() - if lastBlockTimestamp == 0 { - return errors.New("the blockchain is empty") - } - nextBlockTimestamp := lastBlockTimestamp + pool.settings.ValidationTimestamp() - err := blockchainCopy.AddBlock(nextBlockTimestamp, nil, nil) - if err != nil { - return errors.New("failed to add temporary block") - } - timestamp := transaction.Timestamp() - if nextBlockTimestamp < timestamp { - return fmt.Errorf("the transaction timestamp is too far in the future: %v, now: %v", time.Unix(0, timestamp), time.Unix(0, nextBlockTimestamp)) - } - currentBlockTimestamp := lastBlockTimestamp - if timestamp < currentBlockTimestamp { - return fmt.Errorf("the transaction timestamp is too old: %v, current block timestamp: %v", time.Unix(0, timestamp), time.Unix(0, currentBlockTimestamp)) - } - for _, pendingTransaction := range pool.transactions { - if transaction.Equals(pendingTransaction) { - return errors.New("the transaction is already in the transactions pool") - } - } - if err = transaction.VerifySignatures(blockchainCopy.Utxo); err != nil { - return fmt.Errorf("failed to verify transaction: %w", err) - } - _, err = transaction.Fee(pool.settings, nextBlockTimestamp, blockchainCopy.Utxo) - if err != nil { - return fmt.Errorf("failed to verify fee: %w", err) - } - transactions := []*verification.Transaction{transaction} - transactionsBytes, err := json.Marshal(transactions) - if err != nil { - return fmt.Errorf("failed to marshal transactions: %w", err) - } - err = blockchainCopy.AddBlock(nextBlockTimestamp+pool.settings.ValidationTimestamp(), transactionsBytes, nil) - if err != nil { - return fmt.Errorf("next block creation would fail: %w", err) - } - err = blockchainCopy.AddBlock(nextBlockTimestamp+pool.settings.ValidationTimestamp(), nil, nil) - if err != nil { - return fmt.Errorf("later block creation would fail: %w", err) - } - pool.mutex.Lock() - defer pool.mutex.Unlock() - pool.transactions = append(pool.transactions, transaction) - return nil -} - -func (pool *TransactionsPool) clear() { - pool.transactions = nil -} - -func removeTransaction(transactions []*verification.Transaction, removedTransaction *verification.Transaction) []*verification.Transaction { - for i := 0; i < len(transactions); i++ { - if transactions[i] == removedTransaction { - transactions = append(transactions[:i], transactions[i+1:]...) - return transactions - } - } - return transactions -} diff --git a/src/ui/README.md b/src/ui/README.md deleted file mode 100644 index 3083f69b..00000000 --- a/src/ui/README.md +++ /dev/null @@ -1,401 +0,0 @@ -# UI server -The user interface (UI) server lets to have a graphical user interface to easily communicate with a Ruthenium [host node](../node/README.md). -Any other implementation of this UI server can communicate with a node using its [API](../node/README.md#api). -In this repository, the UI is described in a simple `index.html`. Any other implementation of this UI can communicate with the UI server using its [API](#api). - -## Prerequisites -A Ruthenium node must be running. - -## Launch -At root level (ruthenium folder), run the ui using the command `go run src/ui/main.go` with the add of some [program arguments](#program-arguments). For example: -``` -go run src/ui/main.go -host-ip=0.0.0.0 -``` - -## Program arguments: -``` --port: The TCP port number for the UI server (default: "8080") --host-ip: The node host IP or DNS address (default: "127.0.0.1") --host-port: The TCP port number of the host node (accepted values: "10600" for mainnet, "10601" to "10699" for testnet, default: "10600") --templates-path: The UI templates path (default: "templates") --log-level: The log level (accepted values: "debug", "info", "warn", "error", "fatal", default: "info") -``` - -Using a web browser, go to `http://localhost:8080` (If needed, replace `localhost` by the UI server IP address and `8080` by the TCP port number for the UI server) - -## API -Base URL: `:` (example: `localhost:8080`) - -### Transactions pool -
-Add transaction - -![POST](https://img.shields.io/badge/POST-seagreen?style=flat-square) -![/transaction](https://img.shields.io/badge//transaction-dimgray?style=flat-square) - -*Description:* Add a transaction to the transactions pool. -* **parameters:** *none* -* **request body:** [TransactionRequest](#transactionrequest) -* **responses:** - - |Code|Description| - |---|---| - |201|Transaction added| - |400|Bad request, if any request argument is invalid| - |500|Internal server error, if an unexpected condition occurred| -
-
-Get transaction info - -![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) -![/transaction/info](https://img.shields.io/badge//transaction/info-dimgray?style=flat-square) - -*Description:* Get the transaction data needed for a transaction request. -* **parameters:** - - |Name|Description|Example| - |---|---|---| - |`address`|42 characters hexadecimal sender wallet address|`0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a`| - |`value`|64 bits floating-point number value of the transaction|`0`| -* **request body:** *none* -* **responses:** - - |Code|Description| - |---|---| - |200|[TransactionInfo](#transactioninfo)| - |400|Bad request, if any request argument is invalid| - |405|Method not allowed, if the value exceeds the wallet amount for the given address| - |500|Internal server error, if an unexpected condition occurred| -
-
-Get transactions - -![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) -![/transactions](https://img.shields.io/badge//transactions-dimgray?style=flat-square) - -*Description:* Get all the transactions of the current transactions pool. -* **parameters:** *none* -* **request body:** *none* -* **responses:** - - |Code|Description| - |---|---| - |200|Array of [transactions](#transaction)| - |500|Internal server error, if an unexpected condition occurred| -
- -### Wallet -
-Get wallet address - -![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) -![/wallet/address](https://img.shields.io/badge//wallet/address-dimgray?style=flat-square) - -*Description:* Get the wallet address depending on the given public key. -* **parameters:** *none* - - |Name|Description|Example| - |---|---|---| - |`publicKey`|132 characters hexadecimal public key|`0x046bd857ce80ff5238d6561f3a775802453c570b6ea2cbf93a35a8a6542b2edbe5f625f9e3fbd2a5df62adebc27391332a265fb94340fb11b69cf569605a5df782`| -* **request body:** *none* -* **responses:** - - |Code|Description| - |---|---| - |200|42 characters hexadecimal wallet address| - |500|Internal server error, if an unexpected condition occurred| -
-
-Get wallet amount - -![GET](https://img.shields.io/badge/GET-steelblue?style=flat-square) -![/wallet/amount](https://img.shields.io/badge//wallet/amount-dimgray?style=flat-square) - -*Description:* Get the amount for the given wallet address. -* **parameters:** - - |Name|Description|Example| - |---|---|---| - |`address`|42 characters hexadecimal wallet address|`0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a`| -* **request body:** *none* -* **responses:** - - |Code|Description| - |---|---| - |200|64 bits floating-point number amount| - |400|Bad request, if any request argument is invalid| - |500|Internal server error, if an unexpected condition occurred| -
- ---- - -### Schemas - -#### Input - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "output_index": uint16 - "transaction_id": string - "public_key": string - "signature": string -} -``` - - -``` - -The output index -The ID of the transaction holding the output -The output recipient public key -The output signature - -``` - - -``` -{ - "output_index": 0 - "transaction_id": "8ae72a72c0c99dc9d41c2b7d8ea67b5a2de25ff4463b1a53816ba179947ce77d" - "public_key": "0x046bd857ce80ff5238d6561f3a775802453c570b6ea2cbf93a35a8a6542b2edbe5f625f9e3fbd2a5df62adebc27391332a265fb94340fb11b69cf569605a5df782" - "signature": "4f3b24cbb4d2c13aaf60518fce70409fd29e1668db1c2109c0eac58427c203df59788bade6d5f3eb9df161b4ed3de451bac64f4c54e74578d69caf8cd401a38f" -} -``` -
- -#### InputInfo - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "output_index": uint16 - "transaction_id": string -} -``` - - -``` - -The output index -The ID of the transaction holding the output - -``` - - -``` -{ - "output_index": 0 - "transaction_id": "8ae72a72c0c99dc9d41c2b7d8ea67b5a2de25ff4463b1a53816ba179947ce77d" -} -``` -
- -#### Output - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "address": string - "is_yielding": bool - "value": uint64 -} -``` - - -``` - -The address of this output recipient -Whether this output should be used for income calculation -The value at the transaction timestamp - -``` - - -``` -{ - "address": "0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a" - "is_yielding": true - "value": 0 -} -``` -
- -#### Transaction - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "id": string - "inputs": []Input - "outputs": []Output - "timestamp": int64 -} -``` - - -``` - -The ID -The inputs -The outputs -The timestamp - -``` - - -``` -{ - "id": "30148389df42b7cd0cb0d3ce951133da3f36ff4e1581d108da1ee05bacad64b7" - "inputs": [] - "outputs": [] - "timestamp": 1667768884780639700 -} -``` -
- -#### TransactionInfo - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "inputs": []InputInfo - "rest": uint64 - "timestamp": int64 -} -``` - - -``` - -The remaining amount to be used as a value for the output with the sender address -The utxos to be used as inputs of the transaction - -``` - - -``` -{ - "inputs": [] - "rest": 0 - "timestamp": 1667768884780639700 -} -``` -
- -#### TransactionRequest - - - - - - - - - -
-Schema - -Description - -Example -
- -``` -{ - "transaction": Transaction - "transaction_broadcaster_target": string -} -``` - - -``` - -The transaction -The transaction broadcaster target - -``` - - -``` -{ - "transaction": {} - "transaction_broadcaster_target": "0.0.0.0:0000" -} -``` -
diff --git a/src/ui/main.go b/src/ui/main.go deleted file mode 100644 index 0a2f6380..00000000 --- a/src/ui/main.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "encoding/json" - "flag" - "fmt" - "github.com/my-cloud/ruthenium/src/config" - "github.com/my-cloud/ruthenium/src/environment" - "github.com/my-cloud/ruthenium/src/log/console" - "github.com/my-cloud/ruthenium/src/node/clock/tick" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "github.com/my-cloud/ruthenium/src/node/network/p2p/gp2p" - "github.com/my-cloud/ruthenium/src/node/network/p2p/net" - "github.com/my-cloud/ruthenium/src/ui/server/index" - "github.com/my-cloud/ruthenium/src/ui/server/transaction" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/info" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/output/progress" - "github.com/my-cloud/ruthenium/src/ui/server/transactions" - "github.com/my-cloud/ruthenium/src/ui/server/wallet/address" - "github.com/my-cloud/ruthenium/src/ui/server/wallet/amount" - "net/http" - "strconv" - "time" -) - -func main() { - port := flag.Int("port", environment.NewVariable("PORT").GetIntValue(8080), "The TCP port number of the UI server") - hostIp := flag.String("host-ip", environment.NewVariable("HOST_IP").GetStringValue("127.0.0.1"), "The node host IP or DNS address") - hostPort := flag.Int("host-port", environment.NewVariable("HOST_PORT").GetIntValue(10600), "The TCP port number of the host node") - templatesPath := flag.String("templates-path", environment.NewVariable("TEMPLATES_PATH").GetStringValue("templates"), "The UI templates path") - logLevel := flag.String("log-level", environment.NewVariable("LOG_LEVEL").GetStringValue("info"), "The log level (possible values: 'debug', 'info', 'warn', 'error', 'fatal')") - - flag.Parse() - logger := console.NewLogger(console.ParseLevel(*logLevel)) - target := p2p.NewTarget(*hostIp, strconv.Itoa(*hostPort)) - ipFinder := net.NewIpFinder(logger) - clientFactory := gp2p.NewClientFactory(ipFinder, time.Minute) - host, err := p2p.NewNeighbor(target, clientFactory) - if err != nil { - logger.Fatal(fmt.Errorf("unable to find blockchain client: %w", err).Error()) - } - settingsBytes, err := host.GetSettings() - if err != nil { - logger.Fatal(fmt.Errorf("unable to get settings: %w", err).Error()) - } - var settings *config.Settings - err = json.Unmarshal(settingsBytes, &settings) - if err != nil { - logger.Fatal(fmt.Errorf("unable to unmarshal settings: %w", err).Error()) - } - watch := tick.NewWatch() - http.Handle("/", index.NewHandler(*templatesPath, logger)) - http.Handle("/transaction", transaction.NewHandler(host, logger)) - http.Handle("/transactions", transactions.NewHandler(host, logger)) - http.Handle("/transaction/info", info.NewHandler(host, settings, watch, logger)) - http.Handle("/transaction/output/progress", progress.NewHandler(host, settings, watch, logger)) - http.Handle("/wallet/address", address.NewHandler(logger)) - http.Handle("/wallet/amount", amount.NewHandler(host, settings, watch, logger)) - logger.Info("user interface server is running...") - logger.Fatal(http.ListenAndServe("0.0.0.0:"+strconv.Itoa(*port), nil).Error()) -} diff --git a/src/ui/server/index/handler.go b/src/ui/server/index/handler.go deleted file mode 100644 index 3466c926..00000000 --- a/src/ui/server/index/handler.go +++ /dev/null @@ -1,34 +0,0 @@ -package index - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "html/template" - "net/http" - "path" -) - -type Handler struct { - templatesPath string - logger log.Logger -} - -func NewHandler(templatesPath string, logger log.Logger) *Handler { - return &Handler{templatesPath, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - t, err := template.ParseFiles(path.Join(handler.templatesPath, "index.html")) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to parse the template: %w", err).Error()) - return - } - if err = t.Execute(writer, ""); err != nil { - handler.logger.Error(fmt.Errorf("failed to execute the template: %w", err).Error()) - } - default: - handler.logger.Error("invalid HTTP method") - } -} diff --git a/src/ui/server/io_writer.go b/src/ui/server/io_writer.go deleted file mode 100644 index 4606da6b..00000000 --- a/src/ui/server/io_writer.go +++ /dev/null @@ -1,24 +0,0 @@ -package server - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "io" - "net/http" -) - -type IoWriter struct { - writer io.Writer - logger log.Logger -} - -func NewIoWriter(writer http.ResponseWriter, logger log.Logger) *IoWriter { - return &IoWriter{writer, logger} -} - -func (writer *IoWriter) Write(message string) { - i, err := io.WriteString(writer.writer, message) - if err != nil || i == 0 { - writer.logger.Error(fmt.Sprintf("failed to write message: %s", message)) - } -} diff --git a/src/ui/server/settings.go b/src/ui/server/settings.go deleted file mode 100644 index 032520b6..00000000 --- a/src/ui/server/settings.go +++ /dev/null @@ -1,10 +0,0 @@ -package server - -type Settings interface { - HalfLifeInNanoseconds() float64 - IncomeBase() uint64 - IncomeLimit() uint64 - MinimalTransactionFee() uint64 - SmallestUnitsPerCoin() uint64 - ValidationTimestamp() int64 -} diff --git a/src/ui/server/transaction/handler.go b/src/ui/server/transaction/handler.go deleted file mode 100644 index 5f3af09a..00000000 --- a/src/ui/server/transaction/handler.go +++ /dev/null @@ -1,55 +0,0 @@ -package transaction - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol/validation" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server" - "net/http" -) - -type Handler struct { - host network.Neighbor - logger log.Logger -} - -func NewHandler(host network.Neighbor, logger log.Logger) *Handler { - return &Handler{host, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodPost: - jsonWriter := server.NewIoWriter(writer, handler.logger) - decoder := json.NewDecoder(req.Body) - var transaction *verification.Transaction - err := decoder.Decode(&transaction) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to decode transaction: %w", err).Error()) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write("invalid transaction") - return - } - transactionRequest := validation.NewTransactionRequest(transaction, handler.host.Target()) - marshaledTransaction, err := json.Marshal(transactionRequest) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to marshal transaction request: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - err = handler.host.AddTransaction(marshaledTransaction) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to add transaction: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.WriteHeader(http.StatusCreated) - jsonWriter.Write("success") - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} diff --git a/src/ui/server/transaction/info/handler.go b/src/ui/server/transaction/info/handler.go deleted file mode 100644 index afae158b..00000000 --- a/src/ui/server/transaction/info/handler.go +++ /dev/null @@ -1,163 +0,0 @@ -package info - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server" - "math" - "net/http" - "strconv" -) - -type Handler struct { - host network.Neighbor - settings server.Settings - watch clock.Watch - logger log.Logger -} - -func NewHandler(host network.Neighbor, settings server.Settings, watch clock.Watch, logger log.Logger) *Handler { - return &Handler{host, settings, watch, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - jsonWriter := server.NewIoWriter(writer, handler.logger) - address := req.URL.Query().Get("address") - if address == "" { - errorMessage := "address is missing in amount request" - handler.logger.Error(errorMessage) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write(errorMessage) - return - } - requestValue := req.URL.Query().Get("value") - parsedValue, err := strconv.Atoi(requestValue) - if err != nil { - errorMessage := "failed to parse transaction value" - handler.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write(errorMessage) - return - } - requestConsolidation := req.URL.Query().Get("consolidation") - isConsolidationRequired, err := strconv.ParseBool(requestConsolidation) - if err != nil { - errorMessage := "failed to parse consolidation value" - handler.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write(errorMessage) - return - } - utxosBytes, err := handler.host.GetUtxos(address) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var utxos []*verification.Utxo - err = json.Unmarshal(utxosBytes, &utxos) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to unmarshal UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - genesisTimestamp, err := handler.host.GetFirstBlockTimestamp() - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get genesis timestamp: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var selectedInputs []*verification.InputInfo - now := handler.watch.Now().UnixNano() - nextBlockHeight := (now-genesisTimestamp)/handler.settings.ValidationTimestamp() + 1 - nextBlockTimestamp := genesisTimestamp + nextBlockHeight*handler.settings.ValidationTimestamp() - utxosByValue := make(map[uint64][]*verification.InputInfo) - var walletBalance uint64 - var values []uint64 - for _, utxo := range utxos { - utxoValue := utxo.Value(nextBlockTimestamp, handler.settings.HalfLifeInNanoseconds(), handler.settings.IncomeBase(), handler.settings.IncomeLimit()) - walletBalance += utxoValue - if isConsolidationRequired { - selectedInputs = append(selectedInputs, utxo.InputInfo) - } else { - if _, ok := utxosByValue[utxoValue]; !ok { - values = append(values, utxoValue) - } - utxosByValue[utxoValue] = append(utxosByValue[utxoValue], utxo.InputInfo) - } - } - value := uint64(parsedValue) - targetValue := value + handler.settings.MinimalTransactionFee() - if walletBalance < targetValue { - errorMessage := "insufficient wallet balance" - handler.logger.Error(errors.New(errorMessage).Error()) - writer.WriteHeader(http.StatusMethodNotAllowed) - jsonWriter.Write(errorMessage) - return - } - - var inputsValue uint64 - if isConsolidationRequired { - inputsValue = walletBalance - } else if len(values) != 0 { - for inputsValue < targetValue { - closestValueIndex := findClosestValueIndex(targetValue, values) - closestValue := values[closestValueIndex] - values[closestValueIndex] = 0 - closestUtxos := utxosByValue[closestValue] - for i := 0; i < len(closestUtxos) && inputsValue < targetValue; i++ { - inputsValue += closestValue - selectedInputs = append(selectedInputs, closestUtxos[i]) - } - } - } - rest := inputsValue - targetValue - response := &TransactionInfo{ - Rest: rest, - Inputs: selectedInputs, - Timestamp: now, - } - marshaledResponse, err := json.Marshal(response) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to marshal amount: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.Header().Add("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) - server.NewIoWriter(writer, handler.logger).Write(string(marshaledResponse[:])) - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} - -func findClosestValueIndex(target uint64, values []uint64) int { - closestValueIndex := 0 - closestDifference := uint64(math.MaxUint64) - var isTargetSmaller bool - for i, value := range values { - if value < target && isTargetSmaller { - continue - } - var difference uint64 - if value < target { - difference = target - value - } else { - isTargetSmaller = true - difference = value - target - } - if difference < closestDifference { - closestValueIndex = i - closestDifference = difference - } - } - return closestValueIndex -} diff --git a/src/ui/server/transaction/info/transaction_info.go b/src/ui/server/transaction/info/transaction_info.go deleted file mode 100644 index 9379402e..00000000 --- a/src/ui/server/transaction/info/transaction_info.go +++ /dev/null @@ -1,9 +0,0 @@ -package info - -import "github.com/my-cloud/ruthenium/src/node/protocol/verification" - -type TransactionInfo struct { - Inputs []*verification.InputInfo `json:"inputs"` - Rest uint64 `json:"rest"` - Timestamp int64 `json:"timestamp"` -} diff --git a/src/ui/server/transaction/output/progress/handler.go b/src/ui/server/transaction/output/progress/handler.go deleted file mode 100644 index 68651fed..00000000 --- a/src/ui/server/transaction/output/progress/handler.go +++ /dev/null @@ -1,135 +0,0 @@ -package progress - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/output" - "net/http" -) - -type Handler struct { - host network.Neighbor - settings server.Settings - watch clock.Watch - logger log.Logger -} - -func NewHandler(host network.Neighbor, settings server.Settings, watch clock.Watch, logger log.Logger) *Handler { - return &Handler{host, settings, watch, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodPut: - jsonWriter := server.NewIoWriter(writer, handler.logger) - decoder := json.NewDecoder(req.Body) - var searchedUtxo *verification.Utxo - err := decoder.Decode(&searchedUtxo) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to decode utxo: %w", err).Error()) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write("invalid utxo") - return - } - utxosBytes, err := handler.host.GetUtxos(searchedUtxo.Address()) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var utxos []*verification.Utxo - err = json.Unmarshal(utxosBytes, &utxos) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to unmarshal UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - genesisTimestamp, err := handler.host.GetFirstBlockTimestamp() - now := handler.watch.Now().UnixNano() - currentBlockHeight := (now - genesisTimestamp) / handler.settings.ValidationTimestamp() - currentBlockTimestamp := genesisTimestamp + currentBlockHeight*handler.settings.ValidationTimestamp() - progressInfo := &output.ProgressInfo{ - CurrentBlockTimestamp: currentBlockTimestamp, - ValidationTimestamp: handler.settings.ValidationTimestamp(), - } - for _, utxo := range utxos { - if utxo.TransactionId() == searchedUtxo.TransactionId() && utxo.OutputIndex() == searchedUtxo.OutputIndex() { - progressInfo.TransactionStatus = "confirmed" - handler.sendResponse(writer, progressInfo) - return - } - } - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get genesis timestamp: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - blocksBytes, err := handler.host.GetBlocks(uint64(currentBlockHeight)) - if err != nil { - handler.logger.Error("failed to get blocks") - writer.WriteHeader(http.StatusInternalServerError) - return - } - var blocks []*verification.Block - err = json.Unmarshal(blocksBytes, &blocks) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to unmarshal blocks: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - if len(blocks) == 0 { - handler.logger.Error("failed to get last block, get blocks returned an empty list") - writer.WriteHeader(http.StatusInternalServerError) - return - } - for _, validatedTransaction := range blocks[0].Transactions() { - if validatedTransaction.Id() == searchedUtxo.TransactionId() { - progressInfo.TransactionStatus = "validated" - handler.sendResponse(writer, progressInfo) - return - } - } - transactionsBytes, err := handler.host.GetTransactions() - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get transactions: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var transactions []*verification.Transaction - err = json.Unmarshal(transactionsBytes, &transactions) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to unmarshal transactions: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - for _, pendingTransaction := range transactions { - if pendingTransaction.Id() == searchedUtxo.TransactionId() { - progressInfo.TransactionStatus = "sent" - handler.sendResponse(writer, progressInfo) - return - } - } - progressInfo.TransactionStatus = "rejected" - handler.sendResponse(writer, progressInfo) - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} - -func (handler *Handler) sendResponse(writer http.ResponseWriter, progress *output.ProgressInfo) { - marshaledResponse, err := json.Marshal(progress) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to marshal progress: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.Header().Add("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) - server.NewIoWriter(writer, handler.logger).Write(string(marshaledResponse[:])) -} diff --git a/src/ui/server/transactions/handler.go b/src/ui/server/transactions/handler.go deleted file mode 100644 index 17450202..00000000 --- a/src/ui/server/transactions/handler.go +++ /dev/null @@ -1,36 +0,0 @@ -package transactions - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/ui/server" - "net/http" -) - -type Handler struct { - host network.Neighbor - logger log.Logger -} - -func NewHandler(host network.Neighbor, logger log.Logger) *Handler { - return &Handler{host, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - transactions, err := handler.host.GetTransactions() - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get transactions: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.Header().Add("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) - server.NewIoWriter(writer, handler.logger).Write(string(transactions[:])) - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} diff --git a/src/ui/server/wallet/address/handler.go b/src/ui/server/wallet/address/handler.go deleted file mode 100644 index 09db8563..00000000 --- a/src/ui/server/wallet/address/handler.go +++ /dev/null @@ -1,47 +0,0 @@ -package address - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/ui/server" - "net/http" -) - -type Handler struct { - logger log.Logger -} - -func NewHandler(logger log.Logger) *Handler { - return &Handler{logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - jsonWriter := server.NewIoWriter(writer, handler.logger) - publicKeyString := req.URL.Query().Get("publicKey") - publicKey, err := encryption.NewPublicKeyFromHex(publicKeyString) - if err != nil { - errorMessage := "failed to decode public key" - handler.logger.Error(fmt.Errorf("%s: %w", errorMessage, err).Error()) - writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write(errorMessage) - return - } - address := publicKey.Address() - marshaledAddress, err := json.Marshal(address) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to marshal address: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.Header().Add("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) - server.NewIoWriter(writer, handler.logger).Write(string(marshaledAddress[:])) - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} diff --git a/src/ui/server/wallet/amount/handler.go b/src/ui/server/wallet/amount/handler.go deleted file mode 100644 index c6944544..00000000 --- a/src/ui/server/wallet/amount/handler.go +++ /dev/null @@ -1,65 +0,0 @@ -package amount - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/clock" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server" - "net/http" -) - -type Handler struct { - host network.Neighbor - settings server.Settings - watch clock.Watch - logger log.Logger -} - -func NewHandler(host network.Neighbor, settings server.Settings, watch clock.Watch, logger log.Logger) *Handler { - return &Handler{host, settings, watch, logger} -} - -func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { - switch req.Method { - case http.MethodGet: - address := req.URL.Query().Get("address") - if address == "" { - handler.logger.Error("address is missing in amount request") - writer.WriteHeader(http.StatusBadRequest) - return - } - utxosBytes, err := handler.host.GetUtxos(address) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to get UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var utxos []*verification.Utxo - err = json.Unmarshal(utxosBytes, &utxos) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to unmarshal UTXOs: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - var balance uint64 - for _, utxo := range utxos { - now := handler.watch.Now().UnixNano() - balance += utxo.Value(now, handler.settings.HalfLifeInNanoseconds(), handler.settings.IncomeBase(), handler.settings.IncomeLimit()) - } - marshaledAmount, err := json.Marshal(float64(balance) / float64(handler.settings.SmallestUnitsPerCoin())) - if err != nil { - handler.logger.Error(fmt.Errorf("failed to marshal amount: %w", err).Error()) - writer.WriteHeader(http.StatusInternalServerError) - return - } - writer.Header().Add("Content-Type", "application/json") - writer.WriteHeader(http.StatusOK) - server.NewIoWriter(writer, handler.logger).Write(string(marshaledAmount[:])) - default: - handler.logger.Error("invalid HTTP method") - writer.WriteHeader(http.StatusBadRequest) - } -} diff --git a/test/config/settings_test.go b/test/config/settings_test.go deleted file mode 100644 index 2c1ab4d9..00000000 --- a/test/config/settings_test.go +++ /dev/null @@ -1,135 +0,0 @@ -package config - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/config" - "github.com/my-cloud/ruthenium/test" - "os" - "reflect" - "strings" - "testing" - "time" -) - -func Test_NewSettings_UnableToOpenFile_ReturnsError(t *testing.T) { - // Arrange - // Act - _, err := config.NewSettings("") - - // Assert - test.Assert(t, err != nil, "Error is nil whereas it should not.") - if err != nil { - expectedErrorMessage := "unable to open file" - actualErrorMessage := err.Error() - test.Assert(t, strings.Contains(actualErrorMessage, expectedErrorMessage), fmt.Sprintf("Wrong error message.\nExpected: %s\nActual: %s", expectedErrorMessage, actualErrorMessage)) - } -} - -func Test_NewSettings_UnableToUnmarshalBytes_ReturnsError(t *testing.T) { - // Arrange - jsonFile, _ := os.CreateTemp("", "Test_NewSettings_UnableToUnmarshalBytes_ReturnsError.json") - jsonFileName := jsonFile.Name() - defer func() { _ = os.Remove(jsonFileName) }() - jsonData := []byte(`{`) - _, _ = jsonFile.Write(jsonData) - _ = jsonFile.Close() - - // Act - _, err := config.NewSettings(jsonFileName) - - // Assert - test.Assert(t, err != nil, "Error is nil whereas it should not.") - if err != nil { - expectedErrorMessage := "unable to unmarshal" - actualErrorMessage := err.Error() - test.Assert(t, strings.Contains(actualErrorMessage, expectedErrorMessage), fmt.Sprintf("Wrong error message.\nExpected: %s\nActual: %s", expectedErrorMessage, actualErrorMessage)) - } -} - -func Test_NewSettings_ValidBytes_NoError(t *testing.T) { - // Arrange - var blocksCountLimit uint64 = 1 - var genesisAmount uint64 = 2 - var halfLifeInDays float64 = 3 - var incomeBase uint64 = 4 - var incomeLimit uint64 = 5 - var maxOutboundsCount int = 6 - var minimalTransactionFee uint64 = 7 - var smallestUnitsPerCoin uint64 = 8 - var synchronizationIntervalInSeconds int = 9 - var validationIntervalInSeconds int64 = 10 - var validationTimeoutInSeconds int64 = 11 - var verificationsCountPerValidation int64 = 12 - bytes, _ := json.Marshal(struct { - BlocksCountLimit uint64 - GenesisAmount uint64 - HalfLifeInDays float64 - IncomeBase uint64 - IncomeLimit uint64 - MaxOutboundsCount int - MinimalTransactionFee uint64 - SmallestUnitsPerCoin uint64 - SynchronizationIntervalInSeconds int - ValidationIntervalInSeconds int64 - ValidationTimeoutInSeconds int64 - VerificationsCountPerValidation int64 - }{ - BlocksCountLimit: blocksCountLimit, - GenesisAmount: genesisAmount, - HalfLifeInDays: halfLifeInDays, - IncomeBase: incomeBase, - IncomeLimit: incomeLimit, - MaxOutboundsCount: maxOutboundsCount, - MinimalTransactionFee: minimalTransactionFee, - SmallestUnitsPerCoin: smallestUnitsPerCoin, - SynchronizationIntervalInSeconds: synchronizationIntervalInSeconds, - ValidationIntervalInSeconds: validationIntervalInSeconds, - ValidationTimeoutInSeconds: validationTimeoutInSeconds, - VerificationsCountPerValidation: verificationsCountPerValidation, - }) - jsonFile, _ := os.CreateTemp("", "Test_NewSettings_ValidBytes_NoError.json") - jsonFileName := jsonFile.Name() - defer func() { _ = os.Remove(jsonFileName) }() - _, _ = jsonFile.Write(bytes) - _ = jsonFile.Close() - - // Act - settings, _ := config.NewSettings(jsonFileName) - - // Assert - test.Assert(t, settings != nil, "Settings are nil whereas it should not.") - actualBytes := settings.Bytes() - actualBlocksCountLimit := settings.BlocksCountLimit() - actualGenesisAmount := settings.GenesisAmount() - actualHalfLifeInNanoseconds := settings.HalfLifeInNanoseconds() - actualIncomeBase := settings.IncomeBase() - actualIncomeLimit := settings.IncomeLimit() - actualMaxOutboundsCount := settings.MaxOutboundsCount() - actualMinimalTransactionFee := settings.MinimalTransactionFee() - actualSmallestUnitsPerCoin := settings.SmallestUnitsPerCoin() - actualSynchronizationTimer := settings.SynchronizationTimer() - actualValidationTimer := settings.ValidationTimer() - actualValidationTimestamp := settings.ValidationTimestamp() - actualValidationTimeout := settings.ValidationTimeout() - actualVerificationsCountPerValidation := settings.VerificationsCountPerValidation() - test.Assert(t, reflect.DeepEqual(actualBytes, bytes), fmt.Sprintf("wrong bytes. expected: %v, actual: %v", bytes, actualBytes)) - test.Assert(t, actualBlocksCountLimit == blocksCountLimit, fmt.Sprintf("wrong blocksCountLimit. expected: %v, actual: %v", blocksCountLimit, actualBlocksCountLimit)) - test.Assert(t, actualGenesisAmount == genesisAmount, fmt.Sprintf("wrong genesisAmount. expected: %v, actual: %v", genesisAmount, actualGenesisAmount)) - expectedHalfLifeInNanoseconds := halfLifeInDays * 24 * float64(time.Hour.Nanoseconds()) - test.Assert(t, actualHalfLifeInNanoseconds == expectedHalfLifeInNanoseconds, fmt.Sprintf("wrong halfLifeInNanoseconds. expected: %v, actual: %v", expectedHalfLifeInNanoseconds, actualHalfLifeInNanoseconds)) - test.Assert(t, actualIncomeBase == incomeBase, fmt.Sprintf("wrong incomeBase. expected: %v, actual: %v", incomeBase, actualIncomeBase)) - test.Assert(t, actualIncomeLimit == incomeLimit, fmt.Sprintf("wrong incomeLimit. expected: %v, actual: %v", incomeLimit, actualIncomeLimit)) - test.Assert(t, actualMaxOutboundsCount == maxOutboundsCount, fmt.Sprintf("wrong maxOutboundsCount. expected: %v, actual: %v", maxOutboundsCount, actualMaxOutboundsCount)) - test.Assert(t, actualMinimalTransactionFee == minimalTransactionFee, fmt.Sprintf("wrong minimalTransactionFee. expected: %v, actual: %v", minimalTransactionFee, actualMinimalTransactionFee)) - test.Assert(t, actualSmallestUnitsPerCoin == smallestUnitsPerCoin, fmt.Sprintf("wrong smallestUnitsPerCoin. expected: %v, actual: %v", smallestUnitsPerCoin, actualSmallestUnitsPerCoin)) - expectedSynchronizationTimer := time.Duration(synchronizationIntervalInSeconds) * time.Second - test.Assert(t, actualSynchronizationTimer == expectedSynchronizationTimer, fmt.Sprintf("wrong synchronizationTimer. expected: %v, actual: %v", expectedSynchronizationTimer, actualSynchronizationTimer)) - expectedValidationTimer := time.Duration(validationIntervalInSeconds) * time.Second - test.Assert(t, actualValidationTimer == expectedValidationTimer, fmt.Sprintf("wrong validationTimer. expected: %v, actual: %v", expectedValidationTimer, actualValidationTimer)) - expectedValidationTimestamp := validationIntervalInSeconds * time.Second.Nanoseconds() - test.Assert(t, actualValidationTimestamp == expectedValidationTimestamp, fmt.Sprintf("wrong validationTimestamp. expected: %v, actual: %v", expectedValidationTimestamp, actualValidationTimestamp)) - expectedValidationTimeout := time.Duration(validationTimeoutInSeconds) * time.Second - test.Assert(t, actualValidationTimeout == expectedValidationTimeout, fmt.Sprintf("wrong validationTimeout. expected: %v, actual: %v", expectedValidationTimeout, actualValidationTimeout)) - test.Assert(t, actualVerificationsCountPerValidation == verificationsCountPerValidation, fmt.Sprintf("wrong verificationsCountPerValidation. expected: %v, actual: %v", verificationsCountPerValidation, actualVerificationsCountPerValidation)) -} diff --git a/test/node/clock/clocktest/engine_mock.go b/test/node/clock/clocktest/engine_mock.go deleted file mode 100644 index 09880d50..00000000 --- a/test/node/clock/clocktest/engine_mock.go +++ /dev/null @@ -1,175 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package clocktest - -import ( - "github.com/my-cloud/ruthenium/src/node/clock" - "sync" -) - -// Ensure, that EngineMock does implement Engine. -// If this is not the case, regenerate this file with moq. -var _ clock.Engine = &EngineMock{} - -// EngineMock is a mock implementation of Engine. -// -// func TestSomethingThatUsesEngine(t *testing.T) { -// -// // make and configure a mocked Engine -// mockedEngine := &EngineMock{ -// DoFunc: func() { -// panic("mock out the Do method") -// }, -// StartFunc: func() { -// panic("mock out the Start method") -// }, -// StopFunc: func() { -// panic("mock out the Stop method") -// }, -// WaitFunc: func() { -// panic("mock out the Wait method") -// }, -// } -// -// // use mockedEngine in code that requires Engine -// // and then make assertions. -// -// } -type EngineMock struct { - // DoFunc mocks the Do method. - DoFunc func() - - // StartFunc mocks the Start method. - StartFunc func() - - // StopFunc mocks the Stop method. - StopFunc func() - - // WaitFunc mocks the Wait method. - WaitFunc func() - - // calls tracks calls to the methods. - calls struct { - // Do holds details about calls to the Do method. - Do []struct { - } - // Start holds details about calls to the Start method. - Start []struct { - } - // Stop holds details about calls to the Stop method. - Stop []struct { - } - // Wait holds details about calls to the Wait method. - Wait []struct { - } - } - lockDo sync.RWMutex - lockStart sync.RWMutex - lockStop sync.RWMutex - lockWait sync.RWMutex -} - -// Do calls DoFunc. -func (mock *EngineMock) Do() { - if mock.DoFunc == nil { - panic("EngineMock.DoFunc: method is nil but Engine.Do was just called") - } - callInfo := struct { - }{} - mock.lockDo.Lock() - mock.calls.Do = append(mock.calls.Do, callInfo) - mock.lockDo.Unlock() - mock.DoFunc() -} - -// DoCalls gets all the calls that were made to Do. -// Check the length with: -// len(mockedEngine.DoCalls()) -func (mock *EngineMock) DoCalls() []struct { -} { - var calls []struct { - } - mock.lockDo.RLock() - calls = mock.calls.Do - mock.lockDo.RUnlock() - return calls -} - -// Start calls StartFunc. -func (mock *EngineMock) Start() { - if mock.StartFunc == nil { - panic("EngineMock.StartFunc: method is nil but Engine.Start was just called") - } - callInfo := struct { - }{} - mock.lockStart.Lock() - mock.calls.Start = append(mock.calls.Start, callInfo) - mock.lockStart.Unlock() - mock.StartFunc() -} - -// StartCalls gets all the calls that were made to Start. -// Check the length with: -// len(mockedEngine.StartCalls()) -func (mock *EngineMock) StartCalls() []struct { -} { - var calls []struct { - } - mock.lockStart.RLock() - calls = mock.calls.Start - mock.lockStart.RUnlock() - return calls -} - -// Stop calls StopFunc. -func (mock *EngineMock) Stop() { - if mock.StopFunc == nil { - panic("EngineMock.StopFunc: method is nil but Engine.Stop was just called") - } - callInfo := struct { - }{} - mock.lockStop.Lock() - mock.calls.Stop = append(mock.calls.Stop, callInfo) - mock.lockStop.Unlock() - mock.StopFunc() -} - -// StopCalls gets all the calls that were made to Stop. -// Check the length with: -// len(mockedEngine.StopCalls()) -func (mock *EngineMock) StopCalls() []struct { -} { - var calls []struct { - } - mock.lockStop.RLock() - calls = mock.calls.Stop - mock.lockStop.RUnlock() - return calls -} - -// Wait calls WaitFunc. -func (mock *EngineMock) Wait() { - if mock.WaitFunc == nil { - panic("EngineMock.WaitFunc: method is nil but Engine.Wait was just called") - } - callInfo := struct { - }{} - mock.lockWait.Lock() - mock.calls.Wait = append(mock.calls.Wait, callInfo) - mock.lockWait.Unlock() - mock.WaitFunc() -} - -// WaitCalls gets all the calls that were made to Wait. -// Check the length with: -// len(mockedEngine.WaitCalls()) -func (mock *EngineMock) WaitCalls() []struct { -} { - var calls []struct { - } - mock.lockWait.RLock() - calls = mock.calls.Wait - mock.lockWait.RUnlock() - return calls -} diff --git a/test/node/clock/clocktest/watch_mock.go b/test/node/clock/clocktest/watch_mock.go deleted file mode 100644 index be73c296..00000000 --- a/test/node/clock/clocktest/watch_mock.go +++ /dev/null @@ -1,68 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package clocktest - -import ( - "github.com/my-cloud/ruthenium/src/node/clock" - "sync" - "time" -) - -// Ensure, that WatchMock does implement Watch. -// If this is not the case, regenerate this file with moq. -var _ clock.Watch = &WatchMock{} - -// WatchMock is a mock implementation of Watch. -// -// func TestSomethingThatUsesWatch(t *testing.T) { -// -// // make and configure a mocked Watch -// mockedWatch := &WatchMock{ -// NowFunc: func() time.Time { -// panic("mock out the Now method") -// }, -// } -// -// // use mockedWatch in code that requires Watch -// // and then make assertions. -// -// } -type WatchMock struct { - // NowFunc mocks the Now method. - NowFunc func() time.Time - - // calls tracks calls to the methods. - calls struct { - // Now holds details about calls to the Now method. - Now []struct { - } - } - lockNow sync.RWMutex -} - -// Now calls NowFunc. -func (mock *WatchMock) Now() time.Time { - if mock.NowFunc == nil { - panic("WatchMock.NowFunc: method is nil but Watch.Now was just called") - } - callInfo := struct { - }{} - mock.lockNow.Lock() - mock.calls.Now = append(mock.calls.Now, callInfo) - mock.lockNow.Unlock() - return mock.NowFunc() -} - -// NowCalls gets all the calls that were made to Now. -// Check the length with: -// len(mockedWatch.NowCalls()) -func (mock *WatchMock) NowCalls() []struct { -} { - var calls []struct { - } - mock.lockNow.RLock() - calls = mock.calls.Now - mock.lockNow.RUnlock() - return calls -} diff --git a/test/node/network/p2p/gp2p/client_factory_test.go b/test/node/network/p2p/gp2p/client_factory_test.go deleted file mode 100644 index 9a1d0619..00000000 --- a/test/node/network/p2p/gp2p/client_factory_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package gp2p - -import ( - "errors" - "github.com/my-cloud/ruthenium/src/node/network/p2p/gp2p" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "testing" -) - -func Test_CreateClient_IpFinderError_ReturnsNil(t *testing.T) { - // Arrange - ipFinder := new(networktest.IpFinderMock) - ipFinder.LookupIPFunc = func(string) (string, error) { return "", errors.New("") } - clientFactory := gp2p.NewClientFactory(ipFinder, 0) - - // Act - client, _ := clientFactory.CreateClient("", "0") - - // Assert - test.Assert(t, client == nil, "client is not nil whereas it should be") -} - -func Test_CreateClient_ValidIp_ReturnsClient(t *testing.T) { - // Arrange - ipFinder := new(networktest.IpFinderMock) - ipFinder.LookupIPFunc = func(string) (string, error) { return "", nil } - clientFactory := gp2p.NewClientFactory(ipFinder, 0) - - // Act - client, _ := clientFactory.CreateClient("", "0") - - // Assert - test.Assert(t, client != nil, "client is nil whereas it should not") -} diff --git a/test/node/network/p2p/gp2p/handler_test.go b/test/node/network/p2p/gp2p/handler_test.go deleted file mode 100644 index 01356c0e..00000000 --- a/test/node/network/p2p/gp2p/handler_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package gp2p - -import ( - "context" - "encoding/json" - gp2p "github.com/leprosus/golang-p2p" - gp2p2 "github.com/my-cloud/ruthenium/src/node/network/p2p/gp2p" - "github.com/my-cloud/ruthenium/src/node/protocol" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/node/protocol/protocoltest" - "reflect" - "sync" - "testing" - "time" -) - -func Test_HandleTargetsRequest_AddInvalidTargets_AddTargetsNotCalled(t *testing.T) { - // Arrange - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.AddTargetsFunc = func([]string) {} - handler := gp2p2.NewHandler(new(protocoltest.BlockchainMock), nil, synchronizerMock, new(protocoltest.TransactionsPoolMock), new(clocktest.WatchMock), logtest.NewLoggerMock()) - targets := []string{"target"} - marshalledTargets, _ := json.Marshal(targets) - req := gp2p.Data{Bytes: marshalledTargets} - - // Act - _, _ = handler.HandleTargetsRequest(context.TODO(), req) - - // Assert - isMethodCalled := len(synchronizerMock.AddTargetsCalls()) != 0 - test.Assert(t, !isMethodCalled, "Method is called whereas it should not.") -} - -func Test_HandleTargetsRequest_AddValidTargets_AddTargetsCalled(t *testing.T) { - // Arrange - waitGroup := sync.WaitGroup{} - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.AddTargetsFunc = func([]string) { waitGroup.Done() } - handler := gp2p2.NewHandler(new(protocoltest.BlockchainMock), nil, synchronizerMock, new(protocoltest.TransactionsPoolMock), new(clocktest.WatchMock), logtest.NewLoggerMock()) - targets := []string{"target"} - marshalledTargets, _ := json.Marshal(targets) - req := gp2p.Data{Bytes: marshalledTargets} - waitGroup.Add(1) - - // Act - _, _ = handler.HandleTargetsRequest(context.TODO(), req) - - // Assert - waitGroup.Wait() - isMethodCalled := len(synchronizerMock.AddTargetsCalls()) == 1 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} - -func Test_HandleSettingsRequest_ValidRequest_SettingsCalled(t *testing.T) { - // Arrange - expectedSettings := []byte{0} - handler := gp2p2.NewHandler(new(protocoltest.BlockchainMock), expectedSettings, new(networktest.SynchronizerMock), new(protocoltest.TransactionsPoolMock), new(clocktest.WatchMock), logtest.NewLoggerMock()) - req := gp2p.Data{} - - // Act - data, _ := handler.HandleSettingsRequest(context.TODO(), req) - - // Assert - actualSettings := data.GetBytes() - test.Assert(t, reflect.DeepEqual(expectedSettings, actualSettings), "Settings are not the expected ones.") -} - -func Test_HandleFirstBlockTimestampRequest_ValidRequest_FirstBlockTimestampCalled(t *testing.T) { - // Arrange - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.FirstBlockTimestampFunc = func() int64 { return 0 } - handler := gp2p2.NewHandler(blockchainMock, nil, new(networktest.SynchronizerMock), new(protocoltest.TransactionsPoolMock), new(clocktest.WatchMock), logtest.NewLoggerMock()) - req := gp2p.Data{} - - // Act - _, _ = handler.HandleFirstBlockTimestampRequest(context.TODO(), req) - - // Assert - isMethodCalled := len(blockchainMock.FirstBlockTimestampCalls()) != 0 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} - -func Test_HandleTransactionRequest_AddValidTransaction_AddTransactionCalled(t *testing.T) { - // Arrange - waitGroup := sync.WaitGroup{} - transactionsPoolMock := new(protocoltest.TransactionsPoolMock) - transactionsPoolMock.AddTransactionFunc = func([]byte, string) { waitGroup.Done() } - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.HostTargetFunc = func() string { return "" } - handler := gp2p2.NewHandler(new(protocoltest.BlockchainMock), nil, synchronizerMock, transactionsPoolMock, new(clocktest.WatchMock), logtest.NewLoggerMock()) - req := gp2p.Data{} - waitGroup.Add(1) - - // Act - _, _ = handler.HandleTransactionRequest(context.TODO(), req) - - // Assert - waitGroup.Wait() - isMethodCalled := len(transactionsPoolMock.AddTransactionCalls()) == 1 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} - -func Test_HandleUtxosRequest_ValidUtxosRequest_UtxosByAddressCalled(t *testing.T) { - // Arrange - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.UtxosFunc = func(string) []byte { return nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - handler := gp2p2.NewHandler(blockchainMock, nil, new(networktest.SynchronizerMock), new(protocoltest.TransactionsPoolMock), watchMock, logtest.NewLoggerMock()) - address := "address" - marshalledAddress, _ := json.Marshal(&address) - req := gp2p.Data{Bytes: marshalledAddress} - - // Act - _, _ = handler.HandleUtxosRequest(context.TODO(), req) - - // Assert - isMethodCalled := len(blockchainMock.UtxosCalls()) == 1 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} - -func Test_HandleBlocksRequest_ValidBlocksRequest_LastBlocksCalled(t *testing.T) { - // Arrange - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.BlocksFunc = func(uint64) []byte { return nil } - handler := gp2p2.NewHandler(blockchainMock, nil, new(networktest.SynchronizerMock), new(protocoltest.TransactionsPoolMock), new(clocktest.WatchMock), logtest.NewLoggerMock()) - var height uint64 = 0 - marshalledHeight, _ := json.Marshal(&height) - req := gp2p.Data{Bytes: marshalledHeight} - - // Act - _, _ = handler.HandleBlocksRequest(context.TODO(), req) - - // Assert - isMethodCalled := len(blockchainMock.BlocksCalls()) == 1 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} - -func Test_HandleTransactionsRequest_ValidTransactionsRequest_TransactionsCalled(t *testing.T) { - // Arrange - transactionsPoolMock := new(protocoltest.TransactionsPoolMock) - transactionsPoolMock.TransactionsFunc = func() []byte { return nil } - handler := gp2p2.NewHandler(new(protocoltest.BlockchainMock), nil, new(networktest.SynchronizerMock), transactionsPoolMock, new(clocktest.WatchMock), logtest.NewLoggerMock()) - req := gp2p.Data{} - - // Act - _, _ = handler.HandleTransactionsRequest(context.TODO(), req) - - // Assert - isMethodCalled := len(transactionsPoolMock.TransactionsCalls()) == 1 - test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") -} diff --git a/test/node/network/p2p/gp2p/server_factory_test.go b/test/node/network/p2p/gp2p/server_factory_test.go deleted file mode 100644 index 0f0fda48..00000000 --- a/test/node/network/p2p/gp2p/server_factory_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package gp2p - -import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p/gp2p" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/node/network/p2p/p2ptest" - "testing" - "time" -) - -func Test_CreateServer(t *testing.T) { - // Arrange - settings := new(p2ptest.SettingsMock) - settings.ValidationTimeoutFunc = func() time.Duration { return 0 } - serverFactory := gp2p.NewServerFactory(nil, settings) - - // Act - server, _ := serverFactory.CreateServer(0) - - // Assert - test.Assert(t, server != nil, "server is nil whereas it should not") -} diff --git a/test/node/network/p2p/neighbor_test.go b/test/node/network/p2p/neighbor_test.go deleted file mode 100644 index 763aca5e..00000000 --- a/test/node/network/p2p/neighbor_test.go +++ /dev/null @@ -1,322 +0,0 @@ -package p2p - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/node/network/p2p/p2ptest" - "testing" -) - -func Test_Target_NoError_ReturnTarget(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - expectedTargetValue := "ip:port" - target, _ := p2p.NewTargetFromValue(expectedTargetValue) - neighbor, _ := p2p.NewNeighbor(target, clientFactoryMock) - - // Act - actualTargetString := neighbor.Target() - - // Assert - test.Assert(t, actualTargetString == expectedTargetValue, fmt.Sprintf("Wrong target value. expected: %s actual: %s", expectedTargetValue, actualTargetString)) -} - -func Test_GetBlocks_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - var startingBlockHeight uint64 = 0 - - // Act - _, err := neighbor.GetBlocks(startingBlockHeight) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal(&startingBlockHeight) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_GetBlocks_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - var startingBlockHeight uint64 = 0 - - // Act - _, err := neighbor.GetBlocks(startingBlockHeight) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal(&startingBlockHeight) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_GetFirstBlockTimestamp_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - responseBytes, _ := json.Marshal(0) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return responseBytes, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetFirstBlockTimestamp() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_GetFirstBlockTimestamp_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetFirstBlockTimestamp() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_GetFirstBlockTimestamp_UnmarshalError_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetFirstBlockTimestamp() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_SendTargets_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - targets := []string{"target"} - - // Act - err := neighbor.SendTargets(targets) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal(targets) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_SendTargets_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - err := neighbor.SendTargets([]string{}) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal([]string{}) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_GetSettings_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetSettings() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_GetSettings_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetSettings() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_AddTransaction_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - err := neighbor.AddTransaction([]byte{}) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_AddTransaction_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - err := neighbor.AddTransaction([]byte{}) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_GetTransactions_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetTransactions() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_GetTransactions_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - - // Act - _, err := neighbor.GetTransactions() - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} - -func Test_GetUtxos_NoError_ClientCalled(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - expectedAddress := "expected address" - - // Act - _, err := neighbor.GetUtxos(expectedAddress) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal(&expectedAddress) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err == nil, "Error is not nil whereas it should be.") -} - -func Test_GetUtxos_Error_ReturnsError(t *testing.T) { - // Arrange - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, errors.New("") } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - neighbor, _ := p2p.NewNeighbor(new(p2p.Target), clientFactoryMock) - expectedAddress := "expected address" - - // Act - _, err := neighbor.GetUtxos(expectedAddress) - - // Assert - sendCalls := clientMock.SendCalls() - isSendCalledOnce := len(sendCalls) == 1 - test.Assert(t, isSendCalledOnce, "Client is not called a single time whereas it should be.") - req := sendCalls[0].Req - expectedRequestBytes, _ := json.Marshal(&expectedAddress) - test.Assert(t, bytes.Equal(req, expectedRequestBytes), "Client is not called with the good parameter.") - test.Assert(t, err != nil, "Error is nil whereas it should not.") -} diff --git a/test/node/network/p2p/p2ptest/client_factory_mock.go b/test/node/network/p2p/p2ptest/client_factory_mock.go deleted file mode 100644 index 2b208e46..00000000 --- a/test/node/network/p2p/p2ptest/client_factory_mock.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package p2ptest - -import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "sync" -) - -// Ensure, that ClientFactoryMock does implement ClientFactory. -// If this is not the case, regenerate this file with moq. -var _ p2p.ClientFactory = &ClientFactoryMock{} - -// ClientFactoryMock is a mock implementation of ClientFactory. -// -// func TestSomethingThatUsesClientFactory(t *testing.T) { -// -// // make and configure a mocked ClientFactory -// mockedClientFactory := &ClientFactoryMock{ -// CreateClientFunc: func(ip string, port string) (Client, error) { -// panic("mock out the CreateClient method") -// }, -// } -// -// // use mockedClientFactory in code that requires ClientFactory -// // and then make assertions. -// -// } -type ClientFactoryMock struct { - // CreateClientFunc mocks the CreateClient method. - CreateClientFunc func(ip string, port string) (p2p.Client, error) - - // calls tracks calls to the methods. - calls struct { - // CreateClient holds details about calls to the CreateClient method. - CreateClient []struct { - // IP is the ip argument value. - IP string - // Port is the port argument value. - Port string - } - } - lockCreateClient sync.RWMutex -} - -// CreateClient calls CreateClientFunc. -func (mock *ClientFactoryMock) CreateClient(ip string, port string) (p2p.Client, error) { - if mock.CreateClientFunc == nil { - panic("ClientFactoryMock.CreateClientFunc: method is nil but ClientFactory.CreateClient was just called") - } - callInfo := struct { - IP string - Port string - }{ - IP: ip, - Port: port, - } - mock.lockCreateClient.Lock() - mock.calls.CreateClient = append(mock.calls.CreateClient, callInfo) - mock.lockCreateClient.Unlock() - return mock.CreateClientFunc(ip, port) -} - -// CreateClientCalls gets all the calls that were made to CreateClient. -// Check the length with: -// -// len(mockedClientFactory.CreateClientCalls()) -func (mock *ClientFactoryMock) CreateClientCalls() []struct { - IP string - Port string -} { - var calls []struct { - IP string - Port string - } - mock.lockCreateClient.RLock() - calls = mock.calls.CreateClient - mock.lockCreateClient.RUnlock() - return calls -} diff --git a/test/node/network/p2p/p2ptest/client_mock.go b/test/node/network/p2p/p2ptest/client_mock.go deleted file mode 100644 index abfcaa1c..00000000 --- a/test/node/network/p2p/p2ptest/client_mock.go +++ /dev/null @@ -1,81 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package p2ptest - -import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "sync" -) - -// Ensure, that ClientMock does implement Client. -// If this is not the case, regenerate this file with moq. -var _ p2p.Client = &ClientMock{} - -// ClientMock is a mock implementation of Client. -// -// func TestSomethingThatUsesClient(t *testing.T) { -// -// // make and configure a mocked Client -// mockedClient := &ClientMock{ -// SendFunc: func(topic string, req []byte) ([]byte, error) { -// panic("mock out the Send method") -// }, -// } -// -// // use mockedClient in code that requires Client -// // and then make assertions. -// -// } -type ClientMock struct { - // SendFunc mocks the Send method. - SendFunc func(topic string, req []byte) ([]byte, error) - - // calls tracks calls to the methods. - calls struct { - // Send holds details about calls to the Send method. - Send []struct { - // Topic is the topic argument value. - Topic string - // Req is the req argument value. - Req []byte - } - } - lockSend sync.RWMutex -} - -// Send calls SendFunc. -func (mock *ClientMock) Send(topic string, req []byte) ([]byte, error) { - if mock.SendFunc == nil { - panic("ClientMock.SendFunc: method is nil but Client.Send was just called") - } - callInfo := struct { - Topic string - Req []byte - }{ - Topic: topic, - Req: req, - } - mock.lockSend.Lock() - mock.calls.Send = append(mock.calls.Send, callInfo) - mock.lockSend.Unlock() - return mock.SendFunc(topic, req) -} - -// SendCalls gets all the calls that were made to Send. -// Check the length with: -// -// len(mockedClient.SendCalls()) -func (mock *ClientMock) SendCalls() []struct { - Topic string - Req []byte -} { - var calls []struct { - Topic string - Req []byte - } - mock.lockSend.RLock() - calls = mock.calls.Send - mock.lockSend.RUnlock() - return calls -} diff --git a/test/node/network/p2p/p2ptest/handler_mock.go b/test/node/network/p2p/p2ptest/handler_mock.go deleted file mode 100644 index 8563c5de..00000000 --- a/test/node/network/p2p/p2ptest/handler_mock.go +++ /dev/null @@ -1,383 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package p2ptest - -import ( - "context" - gp2p "github.com/leprosus/golang-p2p" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "sync" -) - -// Ensure, that HandlerMock does implement Handler. -// If this is not the case, regenerate this file with moq. -var _ p2p.Handler = &HandlerMock{} - -// HandlerMock is a mock implementation of Handler. -// -// func TestSomethingThatUsesHandler(t *testing.T) { -// -// // make and configure a mocked Handler -// mockedHandler := &HandlerMock{ -// HandleBlocksRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleBlocksRequest method") -// }, -// HandleFirstBlockTimestampRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleFirstBlockTimestampRequest method") -// }, -// HandleSettingsRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleSettingsRequest method") -// }, -// HandleTargetsRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleTargetsRequest method") -// }, -// HandleTransactionRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleTransactionRequest method") -// }, -// HandleTransactionsRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleTransactionsRequest method") -// }, -// HandleUtxosRequestFunc: func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { -// panic("mock out the HandleUtxosRequest method") -// }, -// } -// -// // use mockedHandler in code that requires Handler -// // and then make assertions. -// -// } -type HandlerMock struct { - // HandleBlocksRequestFunc mocks the HandleBlocksRequest method. - HandleBlocksRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleFirstBlockTimestampRequestFunc mocks the HandleFirstBlockTimestampRequest method. - HandleFirstBlockTimestampRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleSettingsRequestFunc mocks the HandleSettingsRequest method. - HandleSettingsRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleTargetsRequestFunc mocks the HandleTargetsRequest method. - HandleTargetsRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleTransactionRequestFunc mocks the HandleTransactionRequest method. - HandleTransactionRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleTransactionsRequestFunc mocks the HandleTransactionsRequest method. - HandleTransactionsRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // HandleUtxosRequestFunc mocks the HandleUtxosRequest method. - HandleUtxosRequestFunc func(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) - - // calls tracks calls to the methods. - calls struct { - // HandleBlocksRequest holds details about calls to the HandleBlocksRequest method. - HandleBlocksRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleFirstBlockTimestampRequest holds details about calls to the HandleFirstBlockTimestampRequest method. - HandleFirstBlockTimestampRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleSettingsRequest holds details about calls to the HandleSettingsRequest method. - HandleSettingsRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleTargetsRequest holds details about calls to the HandleTargetsRequest method. - HandleTargetsRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleTransactionRequest holds details about calls to the HandleTransactionRequest method. - HandleTransactionRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleTransactionsRequest holds details about calls to the HandleTransactionsRequest method. - HandleTransactionsRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - // HandleUtxosRequest holds details about calls to the HandleUtxosRequest method. - HandleUtxosRequest []struct { - // ContextMoqParam is the contextMoqParam argument value. - ContextMoqParam context.Context - // Req is the req argument value. - Req gp2p.Data - } - } - lockHandleBlocksRequest sync.RWMutex - lockHandleFirstBlockTimestampRequest sync.RWMutex - lockHandleSettingsRequest sync.RWMutex - lockHandleTargetsRequest sync.RWMutex - lockHandleTransactionRequest sync.RWMutex - lockHandleTransactionsRequest sync.RWMutex - lockHandleUtxosRequest sync.RWMutex -} - -// HandleBlocksRequest calls HandleBlocksRequestFunc. -func (mock *HandlerMock) HandleBlocksRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleBlocksRequestFunc == nil { - panic("HandlerMock.HandleBlocksRequestFunc: method is nil but Handler.HandleBlocksRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleBlocksRequest.Lock() - mock.calls.HandleBlocksRequest = append(mock.calls.HandleBlocksRequest, callInfo) - mock.lockHandleBlocksRequest.Unlock() - return mock.HandleBlocksRequestFunc(contextMoqParam, req) -} - -// HandleBlocksRequestCalls gets all the calls that were made to HandleBlocksRequest. -// Check the length with: -// -// len(mockedHandler.HandleBlocksRequestCalls()) -func (mock *HandlerMock) HandleBlocksRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleBlocksRequest.RLock() - calls = mock.calls.HandleBlocksRequest - mock.lockHandleBlocksRequest.RUnlock() - return calls -} - -// HandleFirstBlockTimestampRequest calls HandleFirstBlockTimestampRequestFunc. -func (mock *HandlerMock) HandleFirstBlockTimestampRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleFirstBlockTimestampRequestFunc == nil { - panic("HandlerMock.HandleFirstBlockTimestampRequestFunc: method is nil but Handler.HandleFirstBlockTimestampRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleFirstBlockTimestampRequest.Lock() - mock.calls.HandleFirstBlockTimestampRequest = append(mock.calls.HandleFirstBlockTimestampRequest, callInfo) - mock.lockHandleFirstBlockTimestampRequest.Unlock() - return mock.HandleFirstBlockTimestampRequestFunc(contextMoqParam, req) -} - -// HandleFirstBlockTimestampRequestCalls gets all the calls that were made to HandleFirstBlockTimestampRequest. -// Check the length with: -// -// len(mockedHandler.HandleFirstBlockTimestampRequestCalls()) -func (mock *HandlerMock) HandleFirstBlockTimestampRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleFirstBlockTimestampRequest.RLock() - calls = mock.calls.HandleFirstBlockTimestampRequest - mock.lockHandleFirstBlockTimestampRequest.RUnlock() - return calls -} - -// HandleSettingsRequest calls HandleSettingsRequestFunc. -func (mock *HandlerMock) HandleSettingsRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleSettingsRequestFunc == nil { - panic("HandlerMock.HandleSettingsRequestFunc: method is nil but Handler.HandleSettingsRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleSettingsRequest.Lock() - mock.calls.HandleSettingsRequest = append(mock.calls.HandleSettingsRequest, callInfo) - mock.lockHandleSettingsRequest.Unlock() - return mock.HandleSettingsRequestFunc(contextMoqParam, req) -} - -// HandleSettingsRequestCalls gets all the calls that were made to HandleSettingsRequest. -// Check the length with: -// -// len(mockedHandler.HandleSettingsRequestCalls()) -func (mock *HandlerMock) HandleSettingsRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleSettingsRequest.RLock() - calls = mock.calls.HandleSettingsRequest - mock.lockHandleSettingsRequest.RUnlock() - return calls -} - -// HandleTargetsRequest calls HandleTargetsRequestFunc. -func (mock *HandlerMock) HandleTargetsRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleTargetsRequestFunc == nil { - panic("HandlerMock.HandleTargetsRequestFunc: method is nil but Handler.HandleTargetsRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleTargetsRequest.Lock() - mock.calls.HandleTargetsRequest = append(mock.calls.HandleTargetsRequest, callInfo) - mock.lockHandleTargetsRequest.Unlock() - return mock.HandleTargetsRequestFunc(contextMoqParam, req) -} - -// HandleTargetsRequestCalls gets all the calls that were made to HandleTargetsRequest. -// Check the length with: -// -// len(mockedHandler.HandleTargetsRequestCalls()) -func (mock *HandlerMock) HandleTargetsRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleTargetsRequest.RLock() - calls = mock.calls.HandleTargetsRequest - mock.lockHandleTargetsRequest.RUnlock() - return calls -} - -// HandleTransactionRequest calls HandleTransactionRequestFunc. -func (mock *HandlerMock) HandleTransactionRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleTransactionRequestFunc == nil { - panic("HandlerMock.HandleTransactionRequestFunc: method is nil but Handler.HandleTransactionRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleTransactionRequest.Lock() - mock.calls.HandleTransactionRequest = append(mock.calls.HandleTransactionRequest, callInfo) - mock.lockHandleTransactionRequest.Unlock() - return mock.HandleTransactionRequestFunc(contextMoqParam, req) -} - -// HandleTransactionRequestCalls gets all the calls that were made to HandleTransactionRequest. -// Check the length with: -// -// len(mockedHandler.HandleTransactionRequestCalls()) -func (mock *HandlerMock) HandleTransactionRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleTransactionRequest.RLock() - calls = mock.calls.HandleTransactionRequest - mock.lockHandleTransactionRequest.RUnlock() - return calls -} - -// HandleTransactionsRequest calls HandleTransactionsRequestFunc. -func (mock *HandlerMock) HandleTransactionsRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleTransactionsRequestFunc == nil { - panic("HandlerMock.HandleTransactionsRequestFunc: method is nil but Handler.HandleTransactionsRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleTransactionsRequest.Lock() - mock.calls.HandleTransactionsRequest = append(mock.calls.HandleTransactionsRequest, callInfo) - mock.lockHandleTransactionsRequest.Unlock() - return mock.HandleTransactionsRequestFunc(contextMoqParam, req) -} - -// HandleTransactionsRequestCalls gets all the calls that were made to HandleTransactionsRequest. -// Check the length with: -// -// len(mockedHandler.HandleTransactionsRequestCalls()) -func (mock *HandlerMock) HandleTransactionsRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleTransactionsRequest.RLock() - calls = mock.calls.HandleTransactionsRequest - mock.lockHandleTransactionsRequest.RUnlock() - return calls -} - -// HandleUtxosRequest calls HandleUtxosRequestFunc. -func (mock *HandlerMock) HandleUtxosRequest(contextMoqParam context.Context, req gp2p.Data) (gp2p.Data, error) { - if mock.HandleUtxosRequestFunc == nil { - panic("HandlerMock.HandleUtxosRequestFunc: method is nil but Handler.HandleUtxosRequest was just called") - } - callInfo := struct { - ContextMoqParam context.Context - Req gp2p.Data - }{ - ContextMoqParam: contextMoqParam, - Req: req, - } - mock.lockHandleUtxosRequest.Lock() - mock.calls.HandleUtxosRequest = append(mock.calls.HandleUtxosRequest, callInfo) - mock.lockHandleUtxosRequest.Unlock() - return mock.HandleUtxosRequestFunc(contextMoqParam, req) -} - -// HandleUtxosRequestCalls gets all the calls that were made to HandleUtxosRequest. -// Check the length with: -// -// len(mockedHandler.HandleUtxosRequestCalls()) -func (mock *HandlerMock) HandleUtxosRequestCalls() []struct { - ContextMoqParam context.Context - Req gp2p.Data -} { - var calls []struct { - ContextMoqParam context.Context - Req gp2p.Data - } - mock.lockHandleUtxosRequest.RLock() - calls = mock.calls.HandleUtxosRequest - mock.lockHandleUtxosRequest.RUnlock() - return calls -} diff --git a/test/node/network/p2p/p2ptest/settings_mock.go b/test/node/network/p2p/p2ptest/settings_mock.go deleted file mode 100644 index eedf0e45..00000000 --- a/test/node/network/p2p/p2ptest/settings_mock.go +++ /dev/null @@ -1,69 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package p2ptest - -import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "sync" - "time" -) - -// Ensure, that SettingsMock does implement Settings. -// If this is not the case, regenerate this file with moq. -var _ p2p.Settings = &SettingsMock{} - -// SettingsMock is a mock implementation of Settings. -// -// func TestSomethingThatUsesSettings(t *testing.T) { -// -// // make and configure a mocked Settings -// mockedSettings := &SettingsMock{ -// ValidationTimeoutFunc: func() time.Duration { -// panic("mock out the ValidationTimeout method") -// }, -// } -// -// // use mockedSettings in code that requires Settings -// // and then make assertions. -// -// } -type SettingsMock struct { - // ValidationTimeoutFunc mocks the ValidationTimeout method. - ValidationTimeoutFunc func() time.Duration - - // calls tracks calls to the methods. - calls struct { - // ValidationTimeout holds details about calls to the ValidationTimeout method. - ValidationTimeout []struct { - } - } - lockValidationTimeout sync.RWMutex -} - -// ValidationTimeout calls ValidationTimeoutFunc. -func (mock *SettingsMock) ValidationTimeout() time.Duration { - if mock.ValidationTimeoutFunc == nil { - panic("SettingsMock.ValidationTimeoutFunc: method is nil but Settings.ValidationTimeout was just called") - } - callInfo := struct { - }{} - mock.lockValidationTimeout.Lock() - mock.calls.ValidationTimeout = append(mock.calls.ValidationTimeout, callInfo) - mock.lockValidationTimeout.Unlock() - return mock.ValidationTimeoutFunc() -} - -// ValidationTimeoutCalls gets all the calls that were made to ValidationTimeout. -// Check the length with: -// -// len(mockedSettings.ValidationTimeoutCalls()) -func (mock *SettingsMock) ValidationTimeoutCalls() []struct { -} { - var calls []struct { - } - mock.lockValidationTimeout.RLock() - calls = mock.calls.ValidationTimeout - mock.lockValidationTimeout.RUnlock() - return calls -} diff --git a/test/node/network/p2p/synchronizer_test.go b/test/node/network/p2p/synchronizer_test.go deleted file mode 100644 index 18bb5e58..00000000 --- a/test/node/network/p2p/synchronizer_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package p2p - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/p2p/p2ptest" - "testing" - "time" -) - -func Test_AddTargets_MoreThanOneTarget_IncentiveTargetsSender(t *testing.T) { - // Arrange - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Now() } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - scoresBySeedTarget := map[string]int{} - synchronizer := p2p.NewSynchronizer(clientFactoryMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) - target1 := "0.0.0.0:1" - target2 := "0.0.0.0:0" - targetRequests := []string{target1, target2} - - // Act - synchronizer.AddTargets(targetRequests) - - // Assert - synchronizer.Synchronize(0) - neighbors := synchronizer.Neighbors() - expectedNeighborsCount := 1 - test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) - neighborTarget := neighbors[0].Target() - test.Assert(t, neighborTarget == target1, fmt.Sprintf("Wrong neighbor. Expected: %s - Actual: %s", target1, neighborTarget)) -} - -func Test_Incentive_TargetIsNotKnown_TargetIncentive(t *testing.T) { - // Arrange - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Now() } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - scoresBySeedTarget := map[string]int{} - synchronizer := p2p.NewSynchronizer(clientFactoryMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) - expectedTarget := "0.0.0.0:1" - - // Act - synchronizer.Incentive(expectedTarget) - - // Assert - synchronizer.Synchronize(0) - neighbors := synchronizer.Neighbors() - expectedNeighborsCount := 1 - test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) - target := neighbors[0].Target() - test.Assert(t, target == expectedTarget, fmt.Sprintf("Wrong target. Expected: %s - Actual: %s", expectedTarget, target)) -} - -func Test_Synchronize_OneSeed_NeighborAdded(t *testing.T) { - // Arrange - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Now() } - clientFactoryMock := new(p2ptest.ClientFactoryMock) - clientMock := new(p2ptest.ClientMock) - clientMock.SendFunc = func(string, []byte) ([]byte, error) { return []byte{}, nil } - clientFactoryMock.CreateClientFunc = func(string, string) (p2p.Client, error) { return clientMock, nil } - scoresBySeedTarget := map[string]int{"0.0.0.0:1": 0} - synchronizer := p2p.NewSynchronizer(clientFactoryMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) - - // Act - synchronizer.Synchronize(0) - - // Assert - neighbors := synchronizer.Neighbors() - expectedNeighborsCount := 1 - test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) -} diff --git a/test/node/protocol/poh/registry_test.go b/test/node/protocol/poh/registry_test.go deleted file mode 100644 index 3a32a224..00000000 --- a/test/node/protocol/poh/registry_test.go +++ /dev/null @@ -1,23 +0,0 @@ -package poh - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/poh" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "testing" -) - -func Test_IsRegistered_InfuraKey_ReturnsTrue(t *testing.T) { - // Arrange - address := "0x0000000000000000000000000000000000000001" - logger := logtest.NewLoggerMock() - registry := poh.NewRegistry("", logger) - - // Act - isRegistered, err := registry.IsRegistered(address) - fmt.Println(err) - - // Assert - test.Assert(t, isRegistered, "proof of humanity is invalid whereas it should be") -} diff --git a/test/node/protocol/protocoltest/block.go b/test/node/protocol/protocoltest/block.go deleted file mode 100644 index 6244dbdf..00000000 --- a/test/node/protocol/protocoltest/block.go +++ /dev/null @@ -1,17 +0,0 @@ -package protocoltest - -import ( - "github.com/my-cloud/ruthenium/src/node/protocol/verification" -) - -func NewGenesisBlock(validatorWalletAddress string, genesisValue uint64) *verification.Block { - genesisTransaction, _ := verification.NewRewardTransaction(validatorWalletAddress, true, 0, genesisValue) - transactions := []*verification.Transaction{genesisTransaction} - return verification.NewBlock([32]byte{}, nil, nil, 0, transactions) -} - -func NewRewardedBlock(previousHash [32]byte, timestamp int64) *verification.Block { - rewardTransaction, _ := verification.NewRewardTransaction("recipient", false, 0, 0) - transactions := []*verification.Transaction{rewardTransaction} - return verification.NewBlock(previousHash, nil, nil, timestamp, transactions) -} diff --git a/test/node/protocol/protocoltest/blockchain_mock.go b/test/node/protocol/protocoltest/blockchain_mock.go deleted file mode 100644 index 755f1cd5..00000000 --- a/test/node/protocol/protocoltest/blockchain_mock.go +++ /dev/null @@ -1,330 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package protocoltest - -import ( - "github.com/my-cloud/ruthenium/src/node/protocol" - "sync" -) - -// Ensure, that BlockchainMock does implement Blockchain. -// If this is not the case, regenerate this file with moq. -var _ protocol.Blockchain = &BlockchainMock{} - -// BlockchainMock is a mock implementation of Blockchain. -// -// func TestSomethingThatUsesBlockchain(t *testing.T) { -// -// // make and configure a mocked Blockchain -// mockedBlockchain := &BlockchainMock{ -// AddBlockFunc: func(timestamp int64, transactions []byte, newRegisteredAddresses []string) error { -// panic("mock out the AddBlock method") -// }, -// BlocksFunc: func(startingBlockHeight uint64) []byte { -// panic("mock out the Blocks method") -// }, -// CopyFunc: func() Blockchain { -// panic("mock out the Copy method") -// }, -// FirstBlockTimestampFunc: func() int64 { -// panic("mock out the FirstBlockTimestamp method") -// }, -// LastBlockTimestampFunc: func() int64 { -// panic("mock out the LastBlockTimestamp method") -// }, -// UtxoFunc: func(input Input) (Utxo, error) { -// panic("mock out the Utxo method") -// }, -// UtxosFunc: func(address string) []byte { -// panic("mock out the Utxos method") -// }, -// } -// -// // use mockedBlockchain in code that requires Blockchain -// // and then make assertions. -// -// } -type BlockchainMock struct { - // AddBlockFunc mocks the AddBlock method. - AddBlockFunc func(timestamp int64, transactions []byte, newRegisteredAddresses []string) error - - // BlocksFunc mocks the Blocks method. - BlocksFunc func(startingBlockHeight uint64) []byte - - // CopyFunc mocks the Copy method. - CopyFunc func() protocol.Blockchain - - // FirstBlockTimestampFunc mocks the FirstBlockTimestamp method. - FirstBlockTimestampFunc func() int64 - - // LastBlockTimestampFunc mocks the LastBlockTimestamp method. - LastBlockTimestampFunc func() int64 - - // UtxoFunc mocks the Utxo method. - UtxoFunc func(input protocol.InputInfo) (protocol.Utxo, error) - - // UtxosFunc mocks the Utxos method. - UtxosFunc func(address string) []byte - - // calls tracks calls to the methods. - calls struct { - // AddBlock holds details about calls to the AddBlock method. - AddBlock []struct { - // Timestamp is the timestamp argument value. - Timestamp int64 - // Transactions is the transactions argument value. - Transactions []byte - // NewRegisteredAddresses is the newRegisteredAddresses argument value. - NewRegisteredAddresses []string - } - // Blocks holds details about calls to the Blocks method. - Blocks []struct { - // StartingBlockHeight is the startingBlockHeight argument value. - StartingBlockHeight uint64 - } - // Copy holds details about calls to the Copy method. - Copy []struct { - } - // FirstBlockTimestamp holds details about calls to the FirstBlockTimestamp method. - FirstBlockTimestamp []struct { - } - // LastBlockTimestamp holds details about calls to the LastBlockTimestamp method. - LastBlockTimestamp []struct { - } - // Utxo holds details about calls to the Utxo method. - Utxo []struct { - // Input is the input argument value. - Input protocol.InputInfo - } - // Utxos holds details about calls to the Utxos method. - Utxos []struct { - // Address is the address argument value. - Address string - } - } - lockAddBlock sync.RWMutex - lockBlocks sync.RWMutex - lockCopy sync.RWMutex - lockFirstBlockTimestamp sync.RWMutex - lockLastBlockTimestamp sync.RWMutex - lockUtxo sync.RWMutex - lockUtxos sync.RWMutex -} - -// AddBlock calls AddBlockFunc. -func (mock *BlockchainMock) AddBlock(timestamp int64, transactions []byte, newRegisteredAddresses []string) error { - if mock.AddBlockFunc == nil { - panic("BlockchainMock.AddBlockFunc: method is nil but Blockchain.AddBlock was just called") - } - callInfo := struct { - Timestamp int64 - Transactions []byte - NewRegisteredAddresses []string - }{ - Timestamp: timestamp, - Transactions: transactions, - NewRegisteredAddresses: newRegisteredAddresses, - } - mock.lockAddBlock.Lock() - mock.calls.AddBlock = append(mock.calls.AddBlock, callInfo) - mock.lockAddBlock.Unlock() - return mock.AddBlockFunc(timestamp, transactions, newRegisteredAddresses) -} - -// AddBlockCalls gets all the calls that were made to AddBlock. -// Check the length with: -// -// len(mockedBlockchain.AddBlockCalls()) -func (mock *BlockchainMock) AddBlockCalls() []struct { - Timestamp int64 - Transactions []byte - NewRegisteredAddresses []string -} { - var calls []struct { - Timestamp int64 - Transactions []byte - NewRegisteredAddresses []string - } - mock.lockAddBlock.RLock() - calls = mock.calls.AddBlock - mock.lockAddBlock.RUnlock() - return calls -} - -// Blocks calls BlocksFunc. -func (mock *BlockchainMock) Blocks(startingBlockHeight uint64) []byte { - if mock.BlocksFunc == nil { - panic("BlockchainMock.BlocksFunc: method is nil but Blockchain.Blocks was just called") - } - callInfo := struct { - StartingBlockHeight uint64 - }{ - StartingBlockHeight: startingBlockHeight, - } - mock.lockBlocks.Lock() - mock.calls.Blocks = append(mock.calls.Blocks, callInfo) - mock.lockBlocks.Unlock() - return mock.BlocksFunc(startingBlockHeight) -} - -// BlocksCalls gets all the calls that were made to Blocks. -// Check the length with: -// -// len(mockedBlockchain.BlocksCalls()) -func (mock *BlockchainMock) BlocksCalls() []struct { - StartingBlockHeight uint64 -} { - var calls []struct { - StartingBlockHeight uint64 - } - mock.lockBlocks.RLock() - calls = mock.calls.Blocks - mock.lockBlocks.RUnlock() - return calls -} - -// Copy calls CopyFunc. -func (mock *BlockchainMock) Copy() protocol.Blockchain { - if mock.CopyFunc == nil { - panic("BlockchainMock.CopyFunc: method is nil but Blockchain.Copy was just called") - } - callInfo := struct { - }{} - mock.lockCopy.Lock() - mock.calls.Copy = append(mock.calls.Copy, callInfo) - mock.lockCopy.Unlock() - return mock.CopyFunc() -} - -// CopyCalls gets all the calls that were made to Copy. -// Check the length with: -// -// len(mockedBlockchain.CopyCalls()) -func (mock *BlockchainMock) CopyCalls() []struct { -} { - var calls []struct { - } - mock.lockCopy.RLock() - calls = mock.calls.Copy - mock.lockCopy.RUnlock() - return calls -} - -// FirstBlockTimestamp calls FirstBlockTimestampFunc. -func (mock *BlockchainMock) FirstBlockTimestamp() int64 { - if mock.FirstBlockTimestampFunc == nil { - panic("BlockchainMock.FirstBlockTimestampFunc: method is nil but Blockchain.FirstBlockTimestamp was just called") - } - callInfo := struct { - }{} - mock.lockFirstBlockTimestamp.Lock() - mock.calls.FirstBlockTimestamp = append(mock.calls.FirstBlockTimestamp, callInfo) - mock.lockFirstBlockTimestamp.Unlock() - return mock.FirstBlockTimestampFunc() -} - -// FirstBlockTimestampCalls gets all the calls that were made to FirstBlockTimestamp. -// Check the length with: -// -// len(mockedBlockchain.FirstBlockTimestampCalls()) -func (mock *BlockchainMock) FirstBlockTimestampCalls() []struct { -} { - var calls []struct { - } - mock.lockFirstBlockTimestamp.RLock() - calls = mock.calls.FirstBlockTimestamp - mock.lockFirstBlockTimestamp.RUnlock() - return calls -} - -// LastBlockTimestamp calls LastBlockTimestampFunc. -func (mock *BlockchainMock) LastBlockTimestamp() int64 { - if mock.LastBlockTimestampFunc == nil { - panic("BlockchainMock.LastBlockTimestampFunc: method is nil but Blockchain.LastBlockTimestamp was just called") - } - callInfo := struct { - }{} - mock.lockLastBlockTimestamp.Lock() - mock.calls.LastBlockTimestamp = append(mock.calls.LastBlockTimestamp, callInfo) - mock.lockLastBlockTimestamp.Unlock() - return mock.LastBlockTimestampFunc() -} - -// LastBlockTimestampCalls gets all the calls that were made to LastBlockTimestamp. -// Check the length with: -// -// len(mockedBlockchain.LastBlockTimestampCalls()) -func (mock *BlockchainMock) LastBlockTimestampCalls() []struct { -} { - var calls []struct { - } - mock.lockLastBlockTimestamp.RLock() - calls = mock.calls.LastBlockTimestamp - mock.lockLastBlockTimestamp.RUnlock() - return calls -} - -// Utxo calls UtxoFunc. -func (mock *BlockchainMock) Utxo(input protocol.InputInfo) (protocol.Utxo, error) { - if mock.UtxoFunc == nil { - panic("BlockchainMock.UtxoFunc: method is nil but Blockchain.Utxo was just called") - } - callInfo := struct { - Input protocol.InputInfo - }{ - Input: input, - } - mock.lockUtxo.Lock() - mock.calls.Utxo = append(mock.calls.Utxo, callInfo) - mock.lockUtxo.Unlock() - return mock.UtxoFunc(input) -} - -// UtxoCalls gets all the calls that were made to Utxo. -// Check the length with: -// -// len(mockedBlockchain.UtxoCalls()) -func (mock *BlockchainMock) UtxoCalls() []struct { - Input protocol.InputInfo -} { - var calls []struct { - Input protocol.InputInfo - } - mock.lockUtxo.RLock() - calls = mock.calls.Utxo - mock.lockUtxo.RUnlock() - return calls -} - -// Utxos calls UtxosFunc. -func (mock *BlockchainMock) Utxos(address string) []byte { - if mock.UtxosFunc == nil { - panic("BlockchainMock.UtxosFunc: method is nil but Blockchain.Utxos was just called") - } - callInfo := struct { - Address string - }{ - Address: address, - } - mock.lockUtxos.Lock() - mock.calls.Utxos = append(mock.calls.Utxos, callInfo) - mock.lockUtxos.Unlock() - return mock.UtxosFunc(address) -} - -// UtxosCalls gets all the calls that were made to Utxos. -// Check the length with: -// -// len(mockedBlockchain.UtxosCalls()) -func (mock *BlockchainMock) UtxosCalls() []struct { - Address string -} { - var calls []struct { - Address string - } - mock.lockUtxos.RLock() - calls = mock.calls.Utxos - mock.lockUtxos.RUnlock() - return calls -} diff --git a/test/node/protocol/protocoltest/settings_mock.go b/test/node/protocol/protocoltest/settings_mock.go deleted file mode 100644 index bbf6da4e..00000000 --- a/test/node/protocol/protocoltest/settings_mock.go +++ /dev/null @@ -1,328 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package protocoltest - -import ( - "github.com/my-cloud/ruthenium/src/node/protocol" - "sync" - "time" -) - -// Ensure, that SettingsMock does implement Settings. -// If this is not the case, regenerate this file with moq. -var _ protocol.Settings = &SettingsMock{} - -// SettingsMock is a mock implementation of Settings. -// -// func TestSomethingThatUsesSettings(t *testing.T) { -// -// // make and configure a mocked Settings -// mockedSettings := &SettingsMock{ -// BlocksCountLimitFunc: func() uint64 { -// panic("mock out the BlocksCountLimit method") -// }, -// GenesisAmountFunc: func() uint64 { -// panic("mock out the GenesisAmount method") -// }, -// HalfLifeInNanosecondsFunc: func() float64 { -// panic("mock out the HalfLifeInNanoseconds method") -// }, -// IncomeBaseFunc: func() uint64 { -// panic("mock out the IncomeBase method") -// }, -// IncomeLimitFunc: func() uint64 { -// panic("mock out the IncomeLimit method") -// }, -// MinimalTransactionFeeFunc: func() uint64 { -// panic("mock out the MinimalTransactionFee method") -// }, -// ValidationTimeoutFunc: func() time.Duration { -// panic("mock out the ValidationTimeout method") -// }, -// ValidationTimestampFunc: func() int64 { -// panic("mock out the ValidationTimestamp method") -// }, -// } -// -// // use mockedSettings in code that requires Settings -// // and then make assertions. -// -// } -type SettingsMock struct { - // BlocksCountLimitFunc mocks the BlocksCountLimit method. - BlocksCountLimitFunc func() uint64 - - // GenesisAmountFunc mocks the GenesisAmount method. - GenesisAmountFunc func() uint64 - - // HalfLifeInNanosecondsFunc mocks the HalfLifeInNanoseconds method. - HalfLifeInNanosecondsFunc func() float64 - - // IncomeBaseFunc mocks the IncomeBase method. - IncomeBaseFunc func() uint64 - - // IncomeLimitFunc mocks the IncomeLimit method. - IncomeLimitFunc func() uint64 - - // MinimalTransactionFeeFunc mocks the MinimalTransactionFee method. - MinimalTransactionFeeFunc func() uint64 - - // ValidationTimeoutFunc mocks the ValidationTimeout method. - ValidationTimeoutFunc func() time.Duration - - // ValidationTimestampFunc mocks the ValidationTimestamp method. - ValidationTimestampFunc func() int64 - - // calls tracks calls to the methods. - calls struct { - // BlocksCountLimit holds details about calls to the BlocksCountLimit method. - BlocksCountLimit []struct { - } - // GenesisAmount holds details about calls to the GenesisAmount method. - GenesisAmount []struct { - } - // HalfLifeInNanoseconds holds details about calls to the HalfLifeInNanoseconds method. - HalfLifeInNanoseconds []struct { - } - // IncomeBase holds details about calls to the IncomeBase method. - IncomeBase []struct { - } - // IncomeLimit holds details about calls to the IncomeLimit method. - IncomeLimit []struct { - } - // MinimalTransactionFee holds details about calls to the MinimalTransactionFee method. - MinimalTransactionFee []struct { - } - // ValidationTimeout holds details about calls to the ValidationTimeout method. - ValidationTimeout []struct { - } - // ValidationTimestamp holds details about calls to the ValidationTimestamp method. - ValidationTimestamp []struct { - } - } - lockBlocksCountLimit sync.RWMutex - lockGenesisAmount sync.RWMutex - lockHalfLifeInNanoseconds sync.RWMutex - lockIncomeBase sync.RWMutex - lockIncomeLimit sync.RWMutex - lockMinimalTransactionFee sync.RWMutex - lockValidationTimeout sync.RWMutex - lockValidationTimestamp sync.RWMutex -} - -// BlocksCountLimit calls BlocksCountLimitFunc. -func (mock *SettingsMock) BlocksCountLimit() uint64 { - if mock.BlocksCountLimitFunc == nil { - panic("SettingsMock.BlocksCountLimitFunc: method is nil but Settings.BlocksCountLimit was just called") - } - callInfo := struct { - }{} - mock.lockBlocksCountLimit.Lock() - mock.calls.BlocksCountLimit = append(mock.calls.BlocksCountLimit, callInfo) - mock.lockBlocksCountLimit.Unlock() - return mock.BlocksCountLimitFunc() -} - -// BlocksCountLimitCalls gets all the calls that were made to BlocksCountLimit. -// Check the length with: -// -// len(mockedSettings.BlocksCountLimitCalls()) -func (mock *SettingsMock) BlocksCountLimitCalls() []struct { -} { - var calls []struct { - } - mock.lockBlocksCountLimit.RLock() - calls = mock.calls.BlocksCountLimit - mock.lockBlocksCountLimit.RUnlock() - return calls -} - -// GenesisAmount calls GenesisAmountFunc. -func (mock *SettingsMock) GenesisAmount() uint64 { - if mock.GenesisAmountFunc == nil { - panic("SettingsMock.GenesisAmountFunc: method is nil but Settings.GenesisAmount was just called") - } - callInfo := struct { - }{} - mock.lockGenesisAmount.Lock() - mock.calls.GenesisAmount = append(mock.calls.GenesisAmount, callInfo) - mock.lockGenesisAmount.Unlock() - return mock.GenesisAmountFunc() -} - -// GenesisAmountCalls gets all the calls that were made to GenesisAmount. -// Check the length with: -// -// len(mockedSettings.GenesisAmountCalls()) -func (mock *SettingsMock) GenesisAmountCalls() []struct { -} { - var calls []struct { - } - mock.lockGenesisAmount.RLock() - calls = mock.calls.GenesisAmount - mock.lockGenesisAmount.RUnlock() - return calls -} - -// HalfLifeInNanoseconds calls HalfLifeInNanosecondsFunc. -func (mock *SettingsMock) HalfLifeInNanoseconds() float64 { - if mock.HalfLifeInNanosecondsFunc == nil { - panic("SettingsMock.HalfLifeInNanosecondsFunc: method is nil but Settings.HalfLifeInNanoseconds was just called") - } - callInfo := struct { - }{} - mock.lockHalfLifeInNanoseconds.Lock() - mock.calls.HalfLifeInNanoseconds = append(mock.calls.HalfLifeInNanoseconds, callInfo) - mock.lockHalfLifeInNanoseconds.Unlock() - return mock.HalfLifeInNanosecondsFunc() -} - -// HalfLifeInNanosecondsCalls gets all the calls that were made to HalfLifeInNanoseconds. -// Check the length with: -// -// len(mockedSettings.HalfLifeInNanosecondsCalls()) -func (mock *SettingsMock) HalfLifeInNanosecondsCalls() []struct { -} { - var calls []struct { - } - mock.lockHalfLifeInNanoseconds.RLock() - calls = mock.calls.HalfLifeInNanoseconds - mock.lockHalfLifeInNanoseconds.RUnlock() - return calls -} - -// IncomeBase calls IncomeBaseFunc. -func (mock *SettingsMock) IncomeBase() uint64 { - if mock.IncomeBaseFunc == nil { - panic("SettingsMock.IncomeBaseFunc: method is nil but Settings.IncomeBase was just called") - } - callInfo := struct { - }{} - mock.lockIncomeBase.Lock() - mock.calls.IncomeBase = append(mock.calls.IncomeBase, callInfo) - mock.lockIncomeBase.Unlock() - return mock.IncomeBaseFunc() -} - -// IncomeBaseCalls gets all the calls that were made to IncomeBase. -// Check the length with: -// -// len(mockedSettings.IncomeBaseCalls()) -func (mock *SettingsMock) IncomeBaseCalls() []struct { -} { - var calls []struct { - } - mock.lockIncomeBase.RLock() - calls = mock.calls.IncomeBase - mock.lockIncomeBase.RUnlock() - return calls -} - -// IncomeLimit calls IncomeLimitFunc. -func (mock *SettingsMock) IncomeLimit() uint64 { - if mock.IncomeLimitFunc == nil { - panic("SettingsMock.IncomeLimitFunc: method is nil but Settings.IncomeLimit was just called") - } - callInfo := struct { - }{} - mock.lockIncomeLimit.Lock() - mock.calls.IncomeLimit = append(mock.calls.IncomeLimit, callInfo) - mock.lockIncomeLimit.Unlock() - return mock.IncomeLimitFunc() -} - -// IncomeLimitCalls gets all the calls that were made to IncomeLimit. -// Check the length with: -// -// len(mockedSettings.IncomeLimitCalls()) -func (mock *SettingsMock) IncomeLimitCalls() []struct { -} { - var calls []struct { - } - mock.lockIncomeLimit.RLock() - calls = mock.calls.IncomeLimit - mock.lockIncomeLimit.RUnlock() - return calls -} - -// MinimalTransactionFee calls MinimalTransactionFeeFunc. -func (mock *SettingsMock) MinimalTransactionFee() uint64 { - if mock.MinimalTransactionFeeFunc == nil { - panic("SettingsMock.MinimalTransactionFeeFunc: method is nil but Settings.MinimalTransactionFee was just called") - } - callInfo := struct { - }{} - mock.lockMinimalTransactionFee.Lock() - mock.calls.MinimalTransactionFee = append(mock.calls.MinimalTransactionFee, callInfo) - mock.lockMinimalTransactionFee.Unlock() - return mock.MinimalTransactionFeeFunc() -} - -// MinimalTransactionFeeCalls gets all the calls that were made to MinimalTransactionFee. -// Check the length with: -// -// len(mockedSettings.MinimalTransactionFeeCalls()) -func (mock *SettingsMock) MinimalTransactionFeeCalls() []struct { -} { - var calls []struct { - } - mock.lockMinimalTransactionFee.RLock() - calls = mock.calls.MinimalTransactionFee - mock.lockMinimalTransactionFee.RUnlock() - return calls -} - -// ValidationTimeout calls ValidationTimeoutFunc. -func (mock *SettingsMock) ValidationTimeout() time.Duration { - if mock.ValidationTimeoutFunc == nil { - panic("SettingsMock.ValidationTimeoutFunc: method is nil but Settings.ValidationTimeout was just called") - } - callInfo := struct { - }{} - mock.lockValidationTimeout.Lock() - mock.calls.ValidationTimeout = append(mock.calls.ValidationTimeout, callInfo) - mock.lockValidationTimeout.Unlock() - return mock.ValidationTimeoutFunc() -} - -// ValidationTimeoutCalls gets all the calls that were made to ValidationTimeout. -// Check the length with: -// -// len(mockedSettings.ValidationTimeoutCalls()) -func (mock *SettingsMock) ValidationTimeoutCalls() []struct { -} { - var calls []struct { - } - mock.lockValidationTimeout.RLock() - calls = mock.calls.ValidationTimeout - mock.lockValidationTimeout.RUnlock() - return calls -} - -// ValidationTimestamp calls ValidationTimestampFunc. -func (mock *SettingsMock) ValidationTimestamp() int64 { - if mock.ValidationTimestampFunc == nil { - panic("SettingsMock.ValidationTimestampFunc: method is nil but Settings.ValidationTimestamp was just called") - } - callInfo := struct { - }{} - mock.lockValidationTimestamp.Lock() - mock.calls.ValidationTimestamp = append(mock.calls.ValidationTimestamp, callInfo) - mock.lockValidationTimestamp.Unlock() - return mock.ValidationTimestampFunc() -} - -// ValidationTimestampCalls gets all the calls that were made to ValidationTimestamp. -// Check the length with: -// -// len(mockedSettings.ValidationTimestampCalls()) -func (mock *SettingsMock) ValidationTimestampCalls() []struct { -} { - var calls []struct { - } - mock.lockValidationTimestamp.RLock() - calls = mock.calls.ValidationTimestamp - mock.lockValidationTimestamp.RUnlock() - return calls -} diff --git a/test/node/protocol/protocoltest/transaction.go b/test/node/protocol/protocoltest/transaction.go deleted file mode 100644 index 87661a18..00000000 --- a/test/node/protocol/protocoltest/transaction.go +++ /dev/null @@ -1,68 +0,0 @@ -package protocoltest - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" -) - -func NewSignedTransactionRequest(inputsValue uint64, fee uint64, outputIndex uint16, recipientAddress string, privateKey *encryption.PrivateKey, publicKey *encryption.PublicKey, timestamp int64, transactionId string, value uint64, isYielding bool) []byte { - marshalledInput, _ := json.Marshal(struct { - OutputIndex uint16 `json:"output_index"` - TransactionId string `json:"transaction_id"` - }{ - OutputIndex: outputIndex, - TransactionId: transactionId, - }) - signature, _ := encryption.NewSignature(marshalledInput, privateKey) - signatureString := signature.String() - input, _ := verification.NewInput(outputIndex, transactionId, publicKey.String(), signatureString) - sent := verification.NewOutput(recipientAddress, false, value) - restValue := inputsValue - value - fee - rest := verification.NewOutput(recipientAddress, isYielding, restValue) - inputs := []*verification.Input{input} - outputs := []*verification.Output{sent, rest} - id := generateId(inputs, outputs, timestamp) - transaction := struct { - Id string - Inputs []*verification.Input - Outputs []*verification.Output - Timestamp int64 - }{ - Id: id, - Inputs: inputs, - Outputs: outputs, - Timestamp: timestamp, - } - transactionRequest := struct { - Transaction struct { - Id string - Inputs []*verification.Input - Outputs []*verification.Output - Timestamp int64 - } - TransactionBroadcasterTarget string - }{ - Transaction: transaction, - TransactionBroadcasterTarget: "0", - } - marshalledTransactionRequest, _ := json.Marshal(transactionRequest) - return marshalledTransactionRequest -} - -func generateId(inputs []*verification.Input, outputs []*verification.Output, timestamp int64) string { - marshaledTransaction, _ := json.Marshal(struct { - Inputs []*verification.Input `json:"inputs"` - Outputs []*verification.Output `json:"outputs"` - Timestamp int64 `json:"timestamp"` - }{ - Inputs: inputs, - Outputs: outputs, - Timestamp: timestamp, - }) - transactionHash := sha256.Sum256(marshaledTransaction) - id := fmt.Sprintf("%x", transactionHash) - return id -} diff --git a/test/node/protocol/protocoltest/transactions_pool_mock.go b/test/node/protocol/protocoltest/transactions_pool_mock.go deleted file mode 100644 index 38d36e07..00000000 --- a/test/node/protocol/protocoltest/transactions_pool_mock.go +++ /dev/null @@ -1,118 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package protocoltest - -import ( - "github.com/my-cloud/ruthenium/src/node/protocol" - "sync" -) - -// Ensure, that TransactionsPoolMock does implement TransactionsPool. -// If this is not the case, regenerate this file with moq. -var _ protocol.TransactionsPool = &TransactionsPoolMock{} - -// TransactionsPoolMock is a mock implementation of TransactionsPool. -// -// func TestSomethingThatUsesTransactionsPool(t *testing.T) { -// -// // make and configure a mocked TransactionsPool -// mockedTransactionsPool := &TransactionsPoolMock{ -// AddTransactionFunc: func(transaction []byte, hostTarget string) { -// panic("mock out the AddTransaction method") -// }, -// TransactionsFunc: func() []byte { -// panic("mock out the Transactions method") -// }, -// } -// -// // use mockedTransactionsPool in code that requires TransactionsPool -// // and then make assertions. -// -// } -type TransactionsPoolMock struct { - // AddTransactionFunc mocks the AddTransaction method. - AddTransactionFunc func(transaction []byte, hostTarget string) - - // TransactionsFunc mocks the Transactions method. - TransactionsFunc func() []byte - - // calls tracks calls to the methods. - calls struct { - // AddTransaction holds details about calls to the AddTransaction method. - AddTransaction []struct { - // Transaction is the transaction argument value. - Transaction []byte - // HostTarget is the hostTarget argument value. - HostTarget string - } - // Transactions holds details about calls to the Transactions method. - Transactions []struct { - } - } - lockAddTransaction sync.RWMutex - lockTransactions sync.RWMutex -} - -// AddTransaction calls AddTransactionFunc. -func (mock *TransactionsPoolMock) AddTransaction(transaction []byte, hostTarget string) { - if mock.AddTransactionFunc == nil { - panic("TransactionsPoolMock.AddTransactionFunc: method is nil but TransactionsPool.AddTransaction was just called") - } - callInfo := struct { - Transaction []byte - HostTarget string - }{ - Transaction: transaction, - HostTarget: hostTarget, - } - mock.lockAddTransaction.Lock() - mock.calls.AddTransaction = append(mock.calls.AddTransaction, callInfo) - mock.lockAddTransaction.Unlock() - mock.AddTransactionFunc(transaction, hostTarget) -} - -// AddTransactionCalls gets all the calls that were made to AddTransaction. -// Check the length with: -// -// len(mockedTransactionsPool.AddTransactionCalls()) -func (mock *TransactionsPoolMock) AddTransactionCalls() []struct { - Transaction []byte - HostTarget string -} { - var calls []struct { - Transaction []byte - HostTarget string - } - mock.lockAddTransaction.RLock() - calls = mock.calls.AddTransaction - mock.lockAddTransaction.RUnlock() - return calls -} - -// Transactions calls TransactionsFunc. -func (mock *TransactionsPoolMock) Transactions() []byte { - if mock.TransactionsFunc == nil { - panic("TransactionsPoolMock.TransactionsFunc: method is nil but TransactionsPool.Transactions was just called") - } - callInfo := struct { - }{} - mock.lockTransactions.Lock() - mock.calls.Transactions = append(mock.calls.Transactions, callInfo) - mock.lockTransactions.Unlock() - return mock.TransactionsFunc() -} - -// TransactionsCalls gets all the calls that were made to Transactions. -// Check the length with: -// -// len(mockedTransactionsPool.TransactionsCalls()) -func (mock *TransactionsPoolMock) TransactionsCalls() []struct { -} { - var calls []struct { - } - mock.lockTransactions.RLock() - calls = mock.calls.Transactions - mock.lockTransactions.RUnlock() - return calls -} diff --git a/test/node/protocol/validation/transactions_pool_test.go b/test/node/protocol/validation/transactions_pool_test.go deleted file mode 100644 index e6af266b..00000000 --- a/test/node/protocol/validation/transactions_pool_test.go +++ /dev/null @@ -1,396 +0,0 @@ -package validation - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol" - "github.com/my-cloud/ruthenium/src/node/protocol/validation" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/node/protocol/protocoltest" - "testing" - "time" -) - -func Test_AddTransaction_TransactionTimestampIsInTheFuture_TransactionNotAdded(t *testing.T) { - // Arrange - validatorWalletAddress := test.Address - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - watchMock := new(clocktest.WatchMock) - var now int64 = 2 - watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } - logger := logtest.NewLoggerMock() - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, validatorWalletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now+2, "0", genesisValue, false) - - // Act - pool.AddTransaction(transactionRequest, "0") - - // Assert - transactionsBytes := pool.Transactions() - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - expectedTransactionsLength := 0 - actualTransactionsLength := len(transactions) - test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) - test.AssertThatMessageIsLogged(t, []string{"failed to add transaction: the transaction timestamp is too far in the future"}, logger.DebugCalls()) -} - -func Test_AddTransaction_TransactionTimestampIsTooOld_TransactionNotAdded(t *testing.T) { - // Arrange - validatorWalletAddress := test.Address - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - logger := logtest.NewLoggerMock() - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, validatorWalletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now-2, "0", genesisValue, false) - - // Act - pool.AddTransaction(transactionRequest, "0") - - // Assert - transactionsBytes := pool.Transactions() - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - expectedTransactionsLength := 0 - actualTransactionsLength := len(transactions) - test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) - test.AssertThatMessageIsLogged(t, []string{"failed to add transaction: the transaction timestamp is too old"}, logger.DebugCalls()) -} - -func Test_AddTransaction_InvalidSignature_TransactionNotAdded(t *testing.T) { - // Arrange - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress, false, 0), 0), nil - } - var genesisValue uint64 = 0 - privateKey2, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey2) - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, outputIndex, "A", privateKey2, publicKey, now, transactionId, genesisValue, false) - - // Act - pool.AddTransaction(transactionRequest, "0") - - // Assert - transactionsBytes := pool.Transactions() - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - expectedTransactionsLength := 0 - actualTransactionsLength := len(transactions) - test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) - test.AssertThatMessageIsLogged(t, []string{"failed to add transaction: failed to verify transaction: failed to verify signature"}, logger.DebugCalls()) -} - -func Test_AddTransaction_InvalidPublicKey_TransactionNotAdded(t *testing.T) { - // Arrange - walletAddress2 := test.Address2 - neighborMock := new(networktest.NeighborMock) - neighborMock.AddTransactionFunc = func([]byte) error { return nil } - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return []network.Neighbor{neighborMock} } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress2, false, 0), 0), nil - } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, outputIndex, walletAddress2, privateKey, publicKey, now, transactionId, genesisValue, false) - - // Act - pool.AddTransaction(transactionRequest, "0") - - // Assert - transactionsBytes := pool.Transactions() - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - expectedTransactionsLength := 0 - actualTransactionsLength := len(transactions) - test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) - test.AssertThatMessageIsLogged(t, []string{"failed to add transaction: failed to verify transaction: output address does not derive from input public key"}, logger.DebugCalls()) -} - -func Test_AddTransaction_ValidTransaction_TransactionAdded(t *testing.T) { - // Arrange - walletAAddress := test.Address2 - neighborMock := new(networktest.NeighborMock) - neighborMock.AddTransactionFunc = func([]byte) error { return nil } - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return []network.Neighbor{neighborMock} } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress, false, 0), 0), nil - } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, outputIndex, walletAAddress, privateKey, publicKey, now, transactionId, genesisValue, false) - - // Act - pool.AddTransaction(transactionRequest, "0") - - // Assert - transactionsBytes := pool.Transactions() - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - expectedTransactionsLength := 1 - actualTransactionsLength := len(transactions) - test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) -} - -func Test_Validate_BlockAlreadyExist_TransactionsNotValidated(t *testing.T) { - // Arrange - validatorWalletAddress := test.Address - synchronizerMock := new(networktest.SynchronizerMock) - var now int64 = 2 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now } - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, validatorWalletAddress, logger) - - // Act - pool.Validate(now) - - // Assert - test.AssertThatMessageIsLogged(t, []string{"unable to create block, a block with the same timestamp is already in the blockchain"}, logger.ErrorCalls()) -} - -func Test_Validate_BlockIsMissing_TransactionsNotValidated(t *testing.T) { - // Arrange - validatorWalletAddress := test.Address - synchronizerMock := new(networktest.SynchronizerMock) - var now int64 = 3 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 2 } - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, validatorWalletAddress, logger) - - // Act - pool.Validate(now) - - // Assert - test.AssertThatMessageIsLogged(t, []string{"unable to create block, a block is missing in the blockchain"}, logger.ErrorCalls()) -} - -func Test_Validate_TransactionTimestampIsInTheFuture_TransactionsNotValidated(t *testing.T) { - // Arrange - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress, false, 0), 0), nil - } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now+1, "0", genesisValue, false) - pool.AddTransaction(transactionRequest, "0") - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - - // Act - pool.Validate(now) - - // Assert - test.AssertThatMessageIsLogged(t, []string{"transaction removed from the transactions pool, the transaction timestamp is too far in the future"}, logger.WarnCalls()) -} - -func Test_Validate_TransactionTimestampIsTooOld_TransactionsNotValidated(t *testing.T) { - // Arrange - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 3 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 2 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress, false, 0), 0), nil - } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now-2, "0", genesisValue, false) - pool.AddTransaction(transactionRequest, "0") - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - - // Act - pool.Validate(now) - - // Assert - test.AssertThatMessageIsLogged(t, []string{"transaction removed from the transactions pool, the transaction timestamp is too old"}, logger.WarnCalls()) -} - -func Test_Validate_ValidTransaction_TransactionsValidated(t *testing.T) { - // Arrange - synchronizerMock := new(networktest.SynchronizerMock) - synchronizerMock.NeighborsFunc = func() []network.Neighbor { return nil } - synchronizerMock.IncentiveFunc = func(string) {} - var now int64 = 2 - var transactionFee uint64 = 0 - logger := logtest.NewLoggerMock() - blockchainMock := new(protocoltest.BlockchainMock) - blockchainMock.CopyFunc = func() protocol.Blockchain { return blockchainMock } - blockchainMock.LastBlockTimestampFunc = func() int64 { return now - 1 } - blockchainMock.AddBlockFunc = func(int64, []byte, []string) error { return nil } - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - walletAddress := publicKey.Address() - var outputIndex uint16 = 0 - transactionId := "" - blockchainMock.UtxoFunc = func(input protocol.InputInfo) (protocol.Utxo, error) { - inputInfo := verification.NewInputInfo(outputIndex, transactionId) - return verification.NewUtxo(inputInfo, verification.NewOutput(walletAddress, false, 0), 0), nil - } - var genesisValue uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return 1 } - pool := validation.NewTransactionsPool(blockchainMock, settings, synchronizerMock, walletAddress, logger) - transactionRequest := protocoltest.NewSignedTransactionRequest(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now, "0", genesisValue, false) - pool.AddTransaction(transactionRequest, "0") - - // Act - pool.Validate(now) - - // Assert - validatedPool := blockchainMock.AddBlockCalls() - expectedCallsCount := 7 - isTransactionsPoolValidated := len(validatedPool) == expectedCallsCount - test.Assert(t, isTransactionsPoolValidated, fmt.Sprintf("AddBlock method should be called only %d times whereas it's called %d times", expectedCallsCount, len(validatedPool))) - transactionsBytes := validatedPool[expectedCallsCount-1].Transactions - var transactions []*verification.Transaction - _ = json.Unmarshal(transactionsBytes, &transactions) - isTwoTransactions := len(transactions) == 2 - test.Assert(t, isTwoTransactions, "Validated transactions pool should contain exactly 2 transactions.") - actualTransaction := transactions[0] - var expectedTransaction *validation.TransactionRequest - _ = json.Unmarshal(transactionRequest, &expectedTransaction) - test.Assert(t, actualTransaction.Equals(expectedTransaction.Transaction()), "The first validated transaction is not the expected one.") - rewardTransaction := transactions[1] - isRewardTransaction := rewardTransaction.HasReward() - test.Assert(t, isRewardTransaction, "The second validated transaction should be the reward.") -} diff --git a/test/node/protocol/verification/blockchain_test.go b/test/node/protocol/verification/blockchain_test.go deleted file mode 100644 index 3ef4ad52..00000000 --- a/test/node/protocol/verification/blockchain_test.go +++ /dev/null @@ -1,1071 +0,0 @@ -package verification - -import ( - "encoding/json" - "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol/validation" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/node/protocol/protocoltest" - "testing" - "time" -) - -const ( - blockchainReplacedMessage = "verification done: blockchain replaced" - blockchainKeptMessage = "verification done: blockchain kept" -) - -func Test_AddBlock_ValidParameters_NoErrorReturned(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - - // Act - err := blockchain.AddBlock(0, nil, nil) - - // Assert - test.Assert(t, err == nil, "error is returned whereas it should not") -} - -func Test_Blocks_BlocksCountLimitSetToZero_ReturnsEmptyArray(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return 0 } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - - // Act - blocksBytes := blockchain.Blocks(0) - - // Assert - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - test.Assert(t, len(blocks) == 0, "blocks should be empty") -} - -func Test_Blocks_BlocksCountLimitSetToOne_ReturnsOneBlock(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - var expectedBlocksCount uint64 = 1 - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return expectedBlocksCount } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - var validationInterval int64 = 1 - var genesisTimestamp int64 = 0 - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - _ = blockchain.AddBlock(genesisTimestamp+validationInterval, nil, nil) - - // Act - blocksBytes := blockchain.Blocks(0) - - // Assert - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - actualBlocksCount := uint64(len(blocks)) - test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) -} - -func Test_Blocks_BlocksCountLimitSetToTwo_ReturnsTwoBlocks(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - var expectedBlocksCount uint64 = 2 - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return expectedBlocksCount } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - var validationInterval int64 = 1 - var genesisTimestamp int64 = 0 - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - _ = blockchain.AddBlock(genesisTimestamp+validationInterval, nil, nil) - - // Act - blocksBytes := blockchain.Blocks(0) - - // Assert - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - actualBlocksCount := uint64(len(blocks)) - test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) -} - -func Test_Blocks_StartingBlockHeightGreaterThanBlocksLength_ReturnsEmptyArray(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - var blocksCount uint64 = 1 - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return blocksCount } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - var genesisTimestamp int64 = 0 - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - - // Act - blocksBytes := blockchain.Blocks(1) - - // Assert - expectedBlocksCount := 0 - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - actualBlocksCount := len(blocks) - test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) -} - -func Test_FirstBlockTimestamp_BlockchainIsEmpty_Returns0(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - - // Act - actualTimestamp := blockchain.FirstBlockTimestamp() - - // Assert - var expectedTimestamp int64 = 0 - test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) -} - -func Test_FirstBlockTimestamp_BlockchainIsNotEmpty_ReturnsFirstBlockTimestamp(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - var genesisTimestamp int64 = 0 - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - - // Act - actualTimestamp := blockchain.FirstBlockTimestamp() - - // Assert - expectedTimestamp := genesisTimestamp - test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) -} - -func Test_LastBlockTimestamp_BlockchainIsEmpty_Returns0(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - - // Act - actualTimestamp := blockchain.LastBlockTimestamp() - - // Assert - var expectedTimestamp int64 = 0 - test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) -} - -func Test_LastBlockTimestamp_BlockchainIsNotEmpty_ReturnsLastBlockTimestamp(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - settings := new(protocoltest.SettingsMock) - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - var genesisTimestamp int64 = 0 - var expectedTimestamp int64 = 1 - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - _ = blockchain.AddBlock(expectedTimestamp, nil, nil) - - // Act - actualTimestamp := blockchain.LastBlockTimestamp() - - // Assert - test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) -} - -func Test_UtxosByAddress_UnknownAddress_ReturnsEmptyArray(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - genesisValidatorAddress := "" - var genesisAmount uint64 = 0 - settings := new(protocoltest.SettingsMock) - settings.GenesisAmountFunc = func() uint64 { return genesisAmount } - blockchain := verification.NewBlockchain(nil, settings, nil, logger) - - // Act - utxosBytes := blockchain.Utxos(genesisValidatorAddress) - - // Assert - var utxos []*verification.Utxo - _ = json.Unmarshal(utxosBytes, &utxos) - test.Assert(t, len(utxos) == 0, "utxos should be empty") -} - -func Test_Utxos_UtxoExists_ReturnsUtxo(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - synchronizer := new(networktest.SynchronizerMock) - var validationInterval int64 = 1 - settings := new(protocoltest.SettingsMock) - settings.GenesisAmountFunc = func() uint64 { return 1 } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - registeredAddress := "" - var expectedValue uint64 = 1 - var genesisTimestamp int64 = 0 - transaction, _ := verification.NewRewardTransaction(registeredAddress, true, genesisTimestamp+validationInterval, expectedValue) - transactions := []*verification.Transaction{transaction} - transactionsBytes, _ := json.Marshal(transactions) - _ = blockchain.AddBlock(genesisTimestamp, nil, nil) - _ = blockchain.AddBlock(genesisTimestamp+validationInterval, transactionsBytes, []string{registeredAddress}) - _ = blockchain.AddBlock(genesisTimestamp+2*validationInterval, nil, nil) - - // Act - utxosBytes := blockchain.Utxos(registeredAddress) - - // Assert - var utxos []*verification.Utxo - _ = json.Unmarshal(utxosBytes, &utxos) - actualValue := utxos[0].Value(genesisTimestamp+2*validationInterval, 1, 1, 1) - test.Assert(t, actualValue == expectedValue, fmt.Sprintf("utxo amount is %d whereas it should be %d", actualValue, expectedValue)) -} - -func Test_Update_NeighborBlockchainIsBetter_IsReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - var validationTimestamp int64 = 11 - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return 2 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - now := 5 * validationTimestamp - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(now-5*validationTimestamp, nil, nil) - _ = blockchain.AddBlock(now-4*validationTimestamp, nil, nil) - blocksBytes := blockchain.Blocks(0) - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - genesisBlockHash := blocks[1].PreviousHash() - block1 := protocoltest.NewRewardedBlock(genesisBlockHash, now-4*validationTimestamp) - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-3*validationTimestamp) - hash2, _ := block2.Hash() - block3 := protocoltest.NewRewardedBlock(hash2, now-2*validationTimestamp) - hash3, _ := block3.Hash() - block4 := protocoltest.NewRewardedBlock(hash3, now-validationTimestamp) - neighborBlocks := []*verification.Block{blocks[0], block1, block2, block3, block4} - neighborBlocksBytes, _ := json.Marshal(neighborBlocks) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - return neighborBlocksBytes, nil - } - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - blockchainReplacedMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTimestampIsInvalid_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return 1 } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - type args struct { - firstBlockTimestamp int64 - secondBlockTimestamp int64 - } - tests := []struct { - name string - args args - want []int - }{ - { - name: "SecondTimestampBeforeTheFirstOne", - args: args{ - firstBlockTimestamp: 1, - secondBlockTimestamp: 0, - }, - }, - { - name: "BlockMissing", - args: args{ - firstBlockTimestamp: 0, - secondBlockTimestamp: 2, - }, - }, - { - name: "SameZeroedTimestamp", - args: args{ - firstBlockTimestamp: 0, - secondBlockTimestamp: 0, - }, - }, - { - name: "SameNonZeroTimestamp", - args: args{ - firstBlockTimestamp: 1, - secondBlockTimestamp: 1, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - block1 := protocoltest.NewRewardedBlock([32]byte{}, tt.args.firstBlockTimestamp) - hash, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash, tt.args.secondBlockTimestamp) - blocks := []*verification.Block{block1, block2} - blockBytes, _ := json.Marshal(blocks) - return blockBytes, nil - } - - // Act - blockchain.Update(1) - - // Assert - expectedMessages := []string{ - "neighbor block timestamp is invalid", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) - }) - } -} - -func Test_Update_NeighborNewBlockTimestampIsInTheFuture_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var validationTimestamp int64 = 1 - now := validationTimestamp - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - block1 := protocoltest.NewRewardedBlock([32]byte{}, now) - hash, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash, now+validationTimestamp) - blocks := []*verification.Block{block1, block2} - blockBytes, _ := json.Marshal(blocks) - return blockBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "neighbor block timestamp is in the future", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionFeeIsNegative_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var invalidTransactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var incomeLimit uint64 = 1 - genesisAmount := 2 * incomeLimit - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - genesisTransaction := block1.Transactions()[0] - var genesisOutputIndex uint16 = 0 - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, invalidTransactionFee, genesisOutputIndex, "A", privateKey, publicKey, now, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 1) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "transaction fee is negative", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionFeeIsTooLow_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var invalidTransactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - genesisTransaction := block1.Transactions()[0] - var genesisOutputIndex uint16 = 0 - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, invalidTransactionFee, genesisOutputIndex, "A", privateKey, publicKey, now, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 1) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "transaction fee is too low", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionTimestampIsTooFarInTheFuture_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey, publicKey, now+validationTimestamp, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - fmt.Sprintf("a neighbor block transaction timestamp is too far in the future: transaction timestamp: %d, id: %s", invalidTransaction.Timestamp(), invalidTransaction.Id()), - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionTimestampIsTooOld_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey, publicKey, now-validationTimestamp-1, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - fmt.Sprintf("a neighbor block transaction timestamp is too old: transaction timestamp: %d, id: %s", invalidTransaction.Timestamp(), invalidTransaction.Id()), - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionInputSignatureIsInvalid_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - privateKey2, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey2) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey2, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "neighbor transaction is invalid: failed to verify signature of an input: signature is invalid", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborNewBlockTransactionInputPublicKeyIsInvalid_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - address := test.Address - var transactionFee uint64 = 0 - privateKey2, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey2) - publicKey2 := encryption.NewPublicKey(privateKey2) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey2, publicKey2, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, false) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "neighbor transaction is invalid: output address does not derive from input public key", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborAddressIsNotRegistered_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - notRegisteredAddress := test.Address - registry.IsRegisteredFunc = func(address string) (bool, error) { return address != notRegisteredAddress, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - block1 := protocoltest.NewGenesisBlock(notRegisteredAddress, genesisAmount) - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(notRegisteredAddress, false, now, 0) - transactions := []*verification.Transaction{rewardTransaction} - block3 := verification.NewBlock(hash2, []string{notRegisteredAddress}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "validator address is not registered in Proof of Humanity registry", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborBlockRegisteredOutputAddressHasNotBeenAdded_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(address string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - address := test.Address - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - notAddedAddress := test.Address2 - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, notAddedAddress, privateKey, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, true) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, nil, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "an incomed output address is not registered", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborBlockRegisteredOutputAddressHasBeenRemoved_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(address string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var transactionFee uint64 = 0 - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) - publicKey := encryption.NewPublicKey(privateKey) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - var genesisAmount uint64 = 1 - address := test.Address - block1 := protocoltest.NewGenesisBlock(address, genesisAmount) - removedAddress := test.Address2 - var genesisOutputIndex uint16 = 0 - genesisTransaction := block1.Transactions()[0] - invalidTransactionRequestBytes := protocoltest.NewSignedTransactionRequest(genesisAmount, transactionFee, genesisOutputIndex, removedAddress, privateKey, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, true) - var invalidTransactionRequest *validation.TransactionRequest - _ = json.Unmarshal(invalidTransactionRequestBytes, &invalidTransactionRequest) - invalidTransaction := invalidTransactionRequest.Transaction() - hash1, _ := block1.Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - rewardTransaction, _ := verification.NewRewardTransaction(address, false, now, 0) - transactions := []*verification.Transaction{ - invalidTransaction, - rewardTransaction, - } - block3 := verification.NewBlock(hash2, []string{address}, []string{removedAddress}, now, transactions) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - blocks := []*verification.Block{block1, block2, block3} - blocksBytes, _ := json.Marshal(blocks) - return blocksBytes, nil - } - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return transactionFee } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - _ = blockchain.AddBlock(0, nil, nil) - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - "an incomed output address is not registered", - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborValidatorIsNotTheOldest_IsNotReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(address string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return 1 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - rewardTransaction1, _ := verification.NewRewardTransaction(test.Address, false, now-2*validationTimestamp, 0) - transactionsBytes1, _ := json.Marshal([]*verification.Transaction{rewardTransaction1}) - _ = blockchain.AddBlock(now-2*validationTimestamp, transactionsBytes1, nil) - blocksBytes := blockchain.Blocks(0) - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - rewardTransaction2, _ := verification.NewRewardTransaction(test.Address, false, now-validationTimestamp, 0) - transactionsBytes2, _ := json.Marshal([]*verification.Transaction{rewardTransaction2}) - _ = blockchain.AddBlock(now-validationTimestamp, transactionsBytes2, nil) - rewardTransaction3, _ := verification.NewRewardTransaction(test.Address, false, now, 0) - transactionsBytes3, _ := json.Marshal([]*verification.Transaction{rewardTransaction3}) - _ = blockchain.AddBlock(now, transactionsBytes3, nil) - hash1, _ := blocks[0].Hash() - block2 := protocoltest.NewRewardedBlock(hash1, now-validationTimestamp) - hash2, _ := block2.Hash() - block3 := protocoltest.NewRewardedBlock(hash2, now) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - neighborBlocks := []*verification.Block{block3} - neighborBlocksBytes, _ := json.Marshal(neighborBlocks) - return neighborBlocksBytes, nil - } - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - blockchainKeptMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} - -func Test_Update_NeighborValidatorIsTheOldest_IsReplaced(t *testing.T) { - // Arrange - registry := new(protocoltest.RegistryMock) - registry.IsRegisteredFunc = func(address string) (bool, error) { return true, nil } - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - var validationTimestamp int64 = 1 - now := 2 * validationTimestamp - neighborMock.TargetFunc = func() string { - return "neighbor" - } - synchronizer := new(networktest.SynchronizerMock) - synchronizer.NeighborsFunc = func() []network.Neighbor { - return []network.Neighbor{neighborMock} - } - settings := new(protocoltest.SettingsMock) - settings.BlocksCountLimitFunc = func() uint64 { return 2 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 1 } - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 0 } - settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } - settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } - blockchain := verification.NewBlockchain(registry, settings, synchronizer, logger) - rewardTransaction1, _ := verification.NewRewardTransaction(test.Address, false, now-2*validationTimestamp, 0) - transactionsBytes1, _ := json.Marshal([]*verification.Transaction{rewardTransaction1}) - _ = blockchain.AddBlock(now-2*validationTimestamp, transactionsBytes1, nil) - rewardTransaction2, _ := verification.NewRewardTransaction(test.Address, false, now-validationTimestamp, 0) - transactionsBytes2, _ := json.Marshal([]*verification.Transaction{rewardTransaction2}) - _ = blockchain.AddBlock(now-validationTimestamp, transactionsBytes2, nil) - blocksBytes := blockchain.Blocks(0) - var blocks []*verification.Block - _ = json.Unmarshal(blocksBytes, &blocks) - rewardTransaction3, _ := verification.NewRewardTransaction(test.Address, false, now, 0) - transactionsBytes3, _ := json.Marshal([]*verification.Transaction{rewardTransaction3}) - _ = blockchain.AddBlock(now, transactionsBytes3, nil) - hash2, _ := blocks[1].Hash() - block3 := protocoltest.NewRewardedBlock(hash2, now) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { - neighborBlocks := []*verification.Block{block3} - neighborBlocksBytes, _ := json.Marshal(neighborBlocks) - return neighborBlocksBytes, nil - } - - // Act - blockchain.Update(now) - - // Assert - expectedMessages := []string{ - blockchainReplacedMessage, - } - test.AssertThatMessageIsLogged(t, expectedMessages, logger.DebugCalls()) -} diff --git a/test/ui/server/servertest/settings_mock.go b/test/ui/server/servertest/settings_mock.go deleted file mode 100644 index 81bbf305..00000000 --- a/test/ui/server/servertest/settings_mock.go +++ /dev/null @@ -1,253 +0,0 @@ -// Code generated by moq; DO NOT EDIT. -// github.com/matryer/moq - -package servertest - -import ( - "github.com/my-cloud/ruthenium/src/ui/server" - "sync" -) - -// Ensure, that SettingsMock does implement Settings. -// If this is not the case, regenerate this file with moq. -var _ server.Settings = &SettingsMock{} - -// SettingsMock is a mock implementation of Settings. -// -// func TestSomethingThatUsesSettings(t *testing.T) { -// -// // make and configure a mocked Settings -// mockedSettings := &SettingsMock{ -// HalfLifeInNanosecondsFunc: func() float64 { -// panic("mock out the HalfLifeInNanoseconds method") -// }, -// IncomeBaseFunc: func() uint64 { -// panic("mock out the IncomeBase method") -// }, -// IncomeLimitFunc: func() uint64 { -// panic("mock out the IncomeLimit method") -// }, -// MinimalTransactionFeeFunc: func() uint64 { -// panic("mock out the MinimalTransactionFee method") -// }, -// SmallestUnitsPerCoinFunc: func() uint64 { -// panic("mock out the SmallestUnitsPerCoin method") -// }, -// ValidationTimestampFunc: func() int64 { -// panic("mock out the ValidationTimestamp method") -// }, -// } -// -// // use mockedSettings in code that requires Settings -// // and then make assertions. -// -// } -type SettingsMock struct { - // HalfLifeInNanosecondsFunc mocks the HalfLifeInNanoseconds method. - HalfLifeInNanosecondsFunc func() float64 - - // IncomeBaseFunc mocks the IncomeBase method. - IncomeBaseFunc func() uint64 - - // IncomeLimitFunc mocks the IncomeLimit method. - IncomeLimitFunc func() uint64 - - // MinimalTransactionFeeFunc mocks the MinimalTransactionFee method. - MinimalTransactionFeeFunc func() uint64 - - // SmallestUnitsPerCoinFunc mocks the SmallestUnitsPerCoin method. - SmallestUnitsPerCoinFunc func() uint64 - - // ValidationTimestampFunc mocks the ValidationTimestamp method. - ValidationTimestampFunc func() int64 - - // calls tracks calls to the methods. - calls struct { - // HalfLifeInNanoseconds holds details about calls to the HalfLifeInNanoseconds method. - HalfLifeInNanoseconds []struct { - } - // IncomeBase holds details about calls to the IncomeBase method. - IncomeBase []struct { - } - // IncomeLimit holds details about calls to the IncomeLimit method. - IncomeLimit []struct { - } - // MinimalTransactionFee holds details about calls to the MinimalTransactionFee method. - MinimalTransactionFee []struct { - } - // SmallestUnitsPerCoin holds details about calls to the SmallestUnitsPerCoin method. - SmallestUnitsPerCoin []struct { - } - // ValidationTimestamp holds details about calls to the ValidationTimestamp method. - ValidationTimestamp []struct { - } - } - lockHalfLifeInNanoseconds sync.RWMutex - lockIncomeBase sync.RWMutex - lockIncomeLimit sync.RWMutex - lockMinimalTransactionFee sync.RWMutex - lockSmallestUnitsPerCoin sync.RWMutex - lockValidationTimestamp sync.RWMutex -} - -// HalfLifeInNanoseconds calls HalfLifeInNanosecondsFunc. -func (mock *SettingsMock) HalfLifeInNanoseconds() float64 { - if mock.HalfLifeInNanosecondsFunc == nil { - panic("SettingsMock.HalfLifeInNanosecondsFunc: method is nil but Settings.HalfLifeInNanoseconds was just called") - } - callInfo := struct { - }{} - mock.lockHalfLifeInNanoseconds.Lock() - mock.calls.HalfLifeInNanoseconds = append(mock.calls.HalfLifeInNanoseconds, callInfo) - mock.lockHalfLifeInNanoseconds.Unlock() - return mock.HalfLifeInNanosecondsFunc() -} - -// HalfLifeInNanosecondsCalls gets all the calls that were made to HalfLifeInNanoseconds. -// Check the length with: -// -// len(mockedSettings.HalfLifeInNanosecondsCalls()) -func (mock *SettingsMock) HalfLifeInNanosecondsCalls() []struct { -} { - var calls []struct { - } - mock.lockHalfLifeInNanoseconds.RLock() - calls = mock.calls.HalfLifeInNanoseconds - mock.lockHalfLifeInNanoseconds.RUnlock() - return calls -} - -// IncomeBase calls IncomeBaseFunc. -func (mock *SettingsMock) IncomeBase() uint64 { - if mock.IncomeBaseFunc == nil { - panic("SettingsMock.IncomeBaseFunc: method is nil but Settings.IncomeBase was just called") - } - callInfo := struct { - }{} - mock.lockIncomeBase.Lock() - mock.calls.IncomeBase = append(mock.calls.IncomeBase, callInfo) - mock.lockIncomeBase.Unlock() - return mock.IncomeBaseFunc() -} - -// IncomeBaseCalls gets all the calls that were made to IncomeBase. -// Check the length with: -// -// len(mockedSettings.IncomeBaseCalls()) -func (mock *SettingsMock) IncomeBaseCalls() []struct { -} { - var calls []struct { - } - mock.lockIncomeBase.RLock() - calls = mock.calls.IncomeBase - mock.lockIncomeBase.RUnlock() - return calls -} - -// IncomeLimit calls IncomeLimitFunc. -func (mock *SettingsMock) IncomeLimit() uint64 { - if mock.IncomeLimitFunc == nil { - panic("SettingsMock.IncomeLimitFunc: method is nil but Settings.IncomeLimit was just called") - } - callInfo := struct { - }{} - mock.lockIncomeLimit.Lock() - mock.calls.IncomeLimit = append(mock.calls.IncomeLimit, callInfo) - mock.lockIncomeLimit.Unlock() - return mock.IncomeLimitFunc() -} - -// IncomeLimitCalls gets all the calls that were made to IncomeLimit. -// Check the length with: -// -// len(mockedSettings.IncomeLimitCalls()) -func (mock *SettingsMock) IncomeLimitCalls() []struct { -} { - var calls []struct { - } - mock.lockIncomeLimit.RLock() - calls = mock.calls.IncomeLimit - mock.lockIncomeLimit.RUnlock() - return calls -} - -// MinimalTransactionFee calls MinimalTransactionFeeFunc. -func (mock *SettingsMock) MinimalTransactionFee() uint64 { - if mock.MinimalTransactionFeeFunc == nil { - panic("SettingsMock.MinimalTransactionFeeFunc: method is nil but Settings.MinimalTransactionFee was just called") - } - callInfo := struct { - }{} - mock.lockMinimalTransactionFee.Lock() - mock.calls.MinimalTransactionFee = append(mock.calls.MinimalTransactionFee, callInfo) - mock.lockMinimalTransactionFee.Unlock() - return mock.MinimalTransactionFeeFunc() -} - -// MinimalTransactionFeeCalls gets all the calls that were made to MinimalTransactionFee. -// Check the length with: -// -// len(mockedSettings.MinimalTransactionFeeCalls()) -func (mock *SettingsMock) MinimalTransactionFeeCalls() []struct { -} { - var calls []struct { - } - mock.lockMinimalTransactionFee.RLock() - calls = mock.calls.MinimalTransactionFee - mock.lockMinimalTransactionFee.RUnlock() - return calls -} - -// SmallestUnitsPerCoin calls SmallestUnitsPerCoinFunc. -func (mock *SettingsMock) SmallestUnitsPerCoin() uint64 { - if mock.SmallestUnitsPerCoinFunc == nil { - panic("SettingsMock.SmallestUnitsPerCoinFunc: method is nil but Settings.SmallestUnitsPerCoin was just called") - } - callInfo := struct { - }{} - mock.lockSmallestUnitsPerCoin.Lock() - mock.calls.SmallestUnitsPerCoin = append(mock.calls.SmallestUnitsPerCoin, callInfo) - mock.lockSmallestUnitsPerCoin.Unlock() - return mock.SmallestUnitsPerCoinFunc() -} - -// SmallestUnitsPerCoinCalls gets all the calls that were made to SmallestUnitsPerCoin. -// Check the length with: -// -// len(mockedSettings.SmallestUnitsPerCoinCalls()) -func (mock *SettingsMock) SmallestUnitsPerCoinCalls() []struct { -} { - var calls []struct { - } - mock.lockSmallestUnitsPerCoin.RLock() - calls = mock.calls.SmallestUnitsPerCoin - mock.lockSmallestUnitsPerCoin.RUnlock() - return calls -} - -// ValidationTimestamp calls ValidationTimestampFunc. -func (mock *SettingsMock) ValidationTimestamp() int64 { - if mock.ValidationTimestampFunc == nil { - panic("SettingsMock.ValidationTimestampFunc: method is nil but Settings.ValidationTimestamp was just called") - } - callInfo := struct { - }{} - mock.lockValidationTimestamp.Lock() - mock.calls.ValidationTimestamp = append(mock.calls.ValidationTimestamp, callInfo) - mock.lockValidationTimestamp.Unlock() - return mock.ValidationTimestampFunc() -} - -// ValidationTimestampCalls gets all the calls that were made to ValidationTimestamp. -// Check the length with: -// -// len(mockedSettings.ValidationTimestampCalls()) -func (mock *SettingsMock) ValidationTimestampCalls() []struct { -} { - var calls []struct { - } - mock.lockValidationTimestamp.RLock() - calls = mock.calls.ValidationTimestamp - mock.lockValidationTimestamp.RUnlock() - return calls -} diff --git a/test/ui/server/transaction/handler_test.go b/test/ui/server/transaction/handler_test.go deleted file mode 100644 index 1fd75610..00000000 --- a/test/ui/server/transaction/handler_test.go +++ /dev/null @@ -1,111 +0,0 @@ -package transaction - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server/transaction" - "net/http" - "net/http/httptest" - "testing" - - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - neighborMock.TargetFunc = func() string { return "" } - logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) != 0 - test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_UndecipherableTransaction_BadRequest(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, logger) - marshalledData, _ := json.Marshal("") - body := bytes.NewReader(marshalledData) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPost, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) != 0 - test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_NodeError_InternalServerError(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - target := "0.0.0.0:0" - neighborMock.TargetFunc = func() string { return target } - neighborMock.AddTransactionFunc = func([]byte) error { return errors.New("") } - logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, logger) - transactionRequest, _ := verification.NewRewardTransaction("", false, 0, 0) - marshalledTransaction, _ := json.Marshal(transactionRequest) - body := bytes.NewReader(marshalledTransaction) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPost, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_ValidTransaction_NeighborMethodCalled(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - target := "0.0.0.0:0" - neighborMock.TargetFunc = func() string { return target } - neighborMock.AddTransactionFunc = func([]byte) error { return nil } - logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, logger) - transactionRequest, _ := verification.NewRewardTransaction("", false, 0, 0) - marshalledTransaction, _ := json.Marshal(transactionRequest) - body := bytes.NewReader(marshalledTransaction) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPost, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 201 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} diff --git a/test/ui/server/transaction/info/handler_test.go b/test/ui/server/transaction/info/handler_test.go deleted file mode 100644 index aa22f678..00000000 --- a/test/ui/server/transaction/info/handler_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package info - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/info" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/ui/server/servertest" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_InvalidAddress_ReturnsBadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_InvalidValue_ReturnsBadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_IsRegisteredNotProvided_ReturnsBadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetUtxosError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyUtxos, _ := json.Marshal([]*verification.Utxo{}) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, errors.New("") } - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_InsufficientWalletBalance_ReturnsMethodNotAllowed(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyUtxos, _ := json.Marshal([]*verification.Utxo{}) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=0&consolidation=false", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 405 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_ConsolidationNotRequired_ReturnsSomeUtxos(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - - inputInfo1 := verification.NewInputInfo(0, "") - inputInfo2 := verification.NewInputInfo(1, "") - inputInfo3 := verification.NewInputInfo(2, "") - output1 := verification.NewOutput("", false, 1) - output2 := verification.NewOutput("", false, 2) - output3 := verification.NewOutput("", false, 0) - utxos := []*verification.Utxo{ - verification.NewUtxo(inputInfo1, output1, 1), - verification.NewUtxo(inputInfo2, output2, 1), - verification.NewUtxo(inputInfo3, output3, 1), - } - marshalledUtxos, _ := json.Marshal(utxos) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=1&consolidation=false", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - var transactionInfo *info.TransactionInfo - _ = json.Unmarshal(recorder.Body.Bytes(), &transactionInfo) - expectedInputsCount := 1 - actualInputsCount := len(transactionInfo.Inputs) - test.Assert(t, actualInputsCount == expectedInputsCount, fmt.Sprintf("Wrong inputs count. expected: %d actual: %d", expectedInputsCount, actualInputsCount)) -} - -func Test_ServeHTTP_ConsolidationRequired_ReturnsAllUtxos(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - inputInfo1 := verification.NewInputInfo(0, "") - inputInfo2 := verification.NewInputInfo(2, "") - output1 := verification.NewOutput("", false, 1) - output2 := verification.NewOutput("", false, 2) - utxos := []*verification.Utxo{ - verification.NewUtxo(inputInfo1, output1, 1), - verification.NewUtxo(inputInfo2, output2, 1), - } - marshalledUtxos, _ := json.Marshal(utxos) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.MinimalTransactionFeeFunc = func() uint64 { return 1 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := info.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address&value=1&consolidation=true", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - var transactionInfo info.TransactionInfo - _ = json.Unmarshal(recorder.Body.Bytes(), &transactionInfo) - expectedInputsCount := 2 - actualInputsCount := len(transactionInfo.Inputs) - test.Assert(t, actualInputsCount == expectedInputsCount, fmt.Sprintf("Wrong inputs count. expected: %d actual: %d", expectedInputsCount, actualInputsCount)) -} diff --git a/test/ui/server/transaction/output/progress/handler_test.go b/test/ui/server/transaction/output/progress/handler_test.go deleted file mode 100644 index 5db7f564..00000000 --- a/test/ui/server/transaction/output/progress/handler_test.go +++ /dev/null @@ -1,379 +0,0 @@ -package progress - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/output" - "github.com/my-cloud/ruthenium/src/ui/server/transaction/output/progress" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/ui/server/servertest" - "net/http" - "net/http/httptest" - "testing" - "time" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_UndecipherableUtxo_BadRequest(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - watchMock := new(clocktest.WatchMock) - logger := logtest.NewLoggerMock() - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - data := "" - marshalledData, _ := json.Marshal(data) - body := bytes.NewReader(marshalledData) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) != 0 - test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetUtxosError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - utxo := verification.NewUtxo(&verification.InputInfo{}, &verification.Output{}, 0) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, errors.New("") } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - utxo := verification.NewUtxo(&verification.InputInfo{}, &verification.Output{}, 0) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetBlocksError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return nil, errors.New("") } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - utxo := verification.NewUtxo(&verification.InputInfo{}, &verification.Output{}, 0) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetTransactionsError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - blocks := []*verification.Block{verification.NewBlock([32]byte{}, nil, nil, 0, nil)} - marshalledBlocks, _ := json.Marshal(blocks) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } - neighborMock.GetTransactionsFunc = func() ([]byte, error) { return nil, errors.New("") } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - utxo := verification.NewUtxo(&verification.InputInfo{}, &verification.Output{}, 0) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_TransactionNotFound_ReturnsRejected(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - blocks := []*verification.Block{verification.NewBlock([32]byte{}, nil, nil, 0, nil)} - marshalledBlocks, _ := json.Marshal(blocks) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } - neighborMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledEmptyArray, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - utxo := verification.NewUtxo(&verification.InputInfo{}, &verification.Output{}, 0) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - expectedStatus := "rejected" - response := recorder.Body.Bytes() - var progressInfo *output.ProgressInfo - err := json.Unmarshal(response, &progressInfo) - fmt.Println(err) - actualStatus := progressInfo.TransactionStatus - test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) -} - -func Test_ServeHTTP_UtxoFound_ReturnsConfirmed(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - transaction, _ := verification.NewRewardTransaction("", false, 0, 0) - transactionId := transaction.Id() - inputInfo := verification.NewInputInfo(0, transactionId) - utxo := verification.NewUtxo(inputInfo, &verification.Output{}, 0) - marshalledUtxos, _ := json.Marshal([]*verification.Utxo{utxo}) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledUtxos, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - expectedStatus := "confirmed" - response := recorder.Body.Bytes() - var progressInfo *output.ProgressInfo - err := json.Unmarshal(response, &progressInfo) - fmt.Println(err) - actualStatus := progressInfo.TransactionStatus - test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) -} - -func Test_ServeHTTP_ValidatedTransactionFound_ReturnsValidated(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - transaction, _ := verification.NewRewardTransaction("", false, 0, 0) - blocks := []*verification.Block{verification.NewBlock([32]byte{}, nil, nil, 0, []*verification.Transaction{transaction})} - marshalledBlocks, _ := json.Marshal(blocks) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - outputIndex := 0 - utxo := verification.NewUtxo(verification.NewInputInfo(uint16(outputIndex), transaction.Id()), transaction.Outputs()[outputIndex], transaction.Timestamp()) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - expectedStatus := "validated" - response := recorder.Body.Bytes() - var progressInfo *output.ProgressInfo - err := json.Unmarshal(response, &progressInfo) - fmt.Println(err) - actualStatus := progressInfo.TransactionStatus - test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) -} - -func Test_ServeHTTP_PendingTransactionFound_ReturnsSent(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyArray := []byte{91, 93} - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } - neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } - blocks := []*verification.Block{verification.NewBlock([32]byte{}, nil, nil, 0, nil)} - marshalledBlocks, _ := json.Marshal(blocks) - neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } - transaction, _ := verification.NewRewardTransaction("", false, 0, 0) - transactions := []*verification.Transaction{transaction} - marshalledTransactions, _ := json.Marshal(transactions) - neighborMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledTransactions, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := progress.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - outputIndex := 0 - utxo := verification.NewUtxo(verification.NewInputInfo(uint16(outputIndex), transaction.Id()), transaction.Outputs()[outputIndex], transaction.Timestamp()) - marshalledUtxo, _ := json.Marshal(utxo) - body := bytes.NewReader(marshalledUtxo) - request := httptest.NewRequest(http.MethodPut, urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 - test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - expectedStatus := "sent" - response := recorder.Body.Bytes() - var progressInfo *output.ProgressInfo - err := json.Unmarshal(response, &progressInfo) - fmt.Println(err) - actualStatus := progressInfo.TransactionStatus - test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) -} diff --git a/test/ui/server/transactions/handler_test.go b/test/ui/server/transactions/handler_test.go deleted file mode 100644 index 8b8cfaa0..00000000 --- a/test/ui/server/transactions/handler_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package transaction - -import ( - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/ui/server/transactions" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "net/http" - "net/http/httptest" - "testing" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - logger := logtest.NewLoggerMock() - handler := transactions.NewHandler(neighborMock, logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetTransactionsCalls()) != 0 - test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_NodeError_InternalServerError(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - neighborMock.GetTransactionsFunc = func() ([]byte, error) { return nil, errors.New("") } - logger := logtest.NewLoggerMock() - handler := transactions.NewHandler(neighborMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetTransactionsCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_ValidRequest_NeighborMethodCalled(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - neighborMock.GetTransactionsFunc = func() ([]byte, error) { return nil, nil } - logger := logtest.NewLoggerMock() - handler := transactions.NewHandler(neighborMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetTransactionsCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} diff --git a/test/ui/server/wallet/address/handler_test.go b/test/ui/server/wallet/address/handler_test.go deleted file mode 100644 index 018e68d0..00000000 --- a/test/ui/server/wallet/address/handler_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package address - -import ( - "fmt" - "github.com/my-cloud/ruthenium/src/ui/server/wallet/address" - "net/http" - "net/http/httptest" - "testing" - - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - handler := address.NewHandler(logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_InvalidPublicKey_ReturnsBadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - handler := address.NewHandler(logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?publicKey=invalidPublicKey", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_ValidRequest_ReturnsAddress(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - handler := address.NewHandler(logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?publicKey=%s", urlTarget, test.PublicKey), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} diff --git a/test/ui/server/wallet/amount/handler_test.go b/test/ui/server/wallet/amount/handler_test.go deleted file mode 100644 index 9f04a297..00000000 --- a/test/ui/server/wallet/amount/handler_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package address - -import ( - "encoding/json" - "errors" - "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/src/ui/server/wallet/amount" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/networktest" - "github.com/my-cloud/ruthenium/test/ui/server/servertest" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" -) - -const urlTarget = "/url-target" - -func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - handler := amount.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} - for _, method := range invalidHttpMethods { - t.Run(method, func(t *testing.T) { - request := httptest.NewRequest(method, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) - }) - } -} - -func Test_ServeHTTP_InvalidAddress_ReturnsBadRequest(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := amount.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, urlTarget, nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_GetUtxosError_ReturnsInternalServerError(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } - watchMock := new(clocktest.WatchMock) - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := amount.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 500 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_ValidRequest_ReturnsAmount(t *testing.T) { - // Arrange - logger := logtest.NewLoggerMock() - neighborMock := new(networktest.NeighborMock) - marshalledEmptyUtxos, _ := json.Marshal([]*verification.Utxo{}) - neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } - watchMock := new(clocktest.WatchMock) - watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseFunc = func() uint64 { return 0 } - settings.IncomeLimitFunc = func() uint64 { return 0 } - settings.SmallestUnitsPerCoinFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } - handler := amount.NewHandler(neighborMock, settings, watchMock, logger) - recorder := httptest.NewRecorder() - request := httptest.NewRequest(http.MethodGet, fmt.Sprintf("%s?address=address", urlTarget), nil) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) == 1 - test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") - expectedStatusCode := 200 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} diff --git a/src/node/README.md b/validatornode/README.md similarity index 68% rename from src/node/README.md rename to validatornode/README.md index 77e7a09a..7cb5473a 100644 --- a/src/node/README.md +++ b/validatornode/README.md @@ -1,36 +1,159 @@ -# Host node -In this repository, the host node is an implementation following the Ruthenium protocol. Any other implementation can contribute to run the network if it exposes the same [API](#api) and follows the protocol described in the Ruthenium [whitepaper](https://github.com/my-cloud/ruthenium/wiki/Whitepaper). +# Validator Node +In this repository, the validator node is one implementation of the Ruthenium protocol. Any other implementation can contribute to run the network if it exposes the same [API](#api) and follows the protocol described in the Ruthenium [whitepaper](https://github.com/my-cloud/ruthenium/wiki/Whitepaper). ## Prerequisites * A firewall port must be open. The port number will be the value of the `port` [program argument](#program-arguments). * In order to [validate](https://github.com/my-cloud/ruthenium/wiki/Whitepaper#validation) [blocks](https://github.com/my-cloud/ruthenium/wiki/Whitepaper#block) or get an [income](https://github.com/my-cloud/ruthenium/wiki/Whitepaper#income), the node wallet address must be registered in the [Proof of Humanity](https://github.com/my-cloud/ruthenium/wiki/Whitepaper#proof-of-humanity) registry. ## Launch -At root level (ruthenium folder), run the node using the command `go run src/node/main.go` with the add of some [program argument](#program-arguments). For example: +At root level (ruthenium folder), run the validator node using the command `go run validatornode/main.go` with the add of some [program argument](#program-arguments). For example: ``` -go run src/node/main.go -private-key=0x48913790c2bebc48417491f96a7e07ec94c76ccd0fe1562dc1749479d9715afd +go run validatornode/main.go -private-key=0x48913790c2bebc48417491f96a7e07ec94c76ccd0fe1562dc1749479d9715afd ``` -## Program arguments: +## Program Arguments ``` --mnemonic: The mnemonic (required if the private key is not provided) --derivation-path: The derivation path (unused if the mnemonic is omitted, default: "m/44'/60'/0'/0/0") --password: The mnemonic password (unused if the mnemonic is omitted) --private-key: The private key (required if the mnemonic is not provided, unused if the mnemonic is provided) --infura-key: The infura key (required to check the proof of humanity) --ip: The node IP or DNS address (detected if not provided) --port: The TCP port number of the host node (accepted values: "10600" for mainnet, "10601" to "10699" for testnet, default: "10600") --settings-path: The settings file path (default: "config/settings.json") --seeds-path: The seeds file path (default: "config/seeds.json") --log-level: The log level (accepted values: "debug", "info", "warn", "error", "fatal", default: "info") +-settings-path: The settings file path (default: "validatornode/settings.json") ``` - + +## Application Settings + + + + + + + + + +
+Schema + +Description + +Example +
+ +``` +{ + "host": { + "ip": string + "port": int + }, + "network": { + "maxOutboundsCount": int + "seeds": []string + "synchronizationIntervalInSeconds": int + "connectionTimeoutInSeconds": int + }, + "protocol": { + "blocksCountLimit": uint64 + "coinDigitsCount": uint8 + "genesisAmount": uint64 + "halfLifeInDays": float64 + "incomeBase": uint64 + "incomeLimit": uint64 + "minimalTransactionFee": uint64 + "validationIntervalInSeconds": int64 + "validationTimeoutInSeconds": int64 + "verificationsCountPerValidation": int64 + }, + "registry": { + "synchronizationIntervalInSeconds": int + }, + "validator": { + "address": string + "infuraKey": string + }, + "log": { + "level": string + } +} +``` + + +``` + + +The validator node IP or DNS address (detected if not provided) +The validator node TCP port number (accepted values: "10600" for mainnet, "10601" to "10699" for testnet) + + +The maximum validator node outbounds count +The initial validator node neighbors +The neighbors blockchain synchronization interval in seconds +The neighbors connection timeout in seconds + + +The maximum returned blocks for a blocks request +The coin digits count +The genesis transaction reward amount +The coin half-life +The income amount after a period of one half-life for an empty initial balance +The balance limit to receive the income +The minimal transaction fee +The validation interval in seconds +The validation timeout in seconds +The verifications count per validation + + +The synchronization interval in seconds + + +The validator wallet address +The infura key (required to check the proof of humanity) + + +The log level (accepted values: "debug", "info", "warn", "error", "fatal") + + +``` + + +``` +{ + "host": { + "ip": "", + "port": 10600 + }, + "network": { + "maxOutboundsCount": 8, + "seeds": ["seed-styx.ruthenium.my-cloud.me:10600"], + "synchronizationIntervalInSeconds": 6, + "connectionTimeoutInSeconds": 3 + }, + "protocol": { + "blocksCountLimit": 1440, + "coinDigitsCount": 8, + "genesisAmount": 5000000000000, + "halfLifeInDays": 373.59, + "incomeBase": 100000000000, + "incomeLimit": 5000000000000, + "minimalTransactionFee": 1000, + "validationIntervalInSeconds": 3, + "validationTimeoutInSeconds": 3, + "verificationsCountPerValidation": 6 + }, + "registry": { + "synchronizationIntervalInSeconds": 3600 + }, + "validator": { + "address": "0xf14DB86A3292ABaB1D4B912dbF55e8abc112593a", + "infuraKey": "b41e3l513a654f92a5c6bb273e62a91c" + }, + "log": { + "level": "info" + } +} +``` +
+ ## API -Base URL: `:` (example: seed-styx.ruthenium.my-cloud.me:10600) +Base URL: `:` (example: seed-styx.ruthenium.my-cloud.me:10600) Each request value or response value shall be marshaled to bytes or un-marshaled from bytes. All fields are required. -### Blockchain +### History
Get blocks @@ -61,18 +184,7 @@ Each request value or response value shall be marshaled to bytes or un-marshaled * **response value:** *none*
-### Node -
-Get settings - -![/settings](https://img.shields.io/badge//settings-dimgray?style=flat-square) - -*Description:* Get node settings. -* **request value:** [settings](#settings) -* **response value:** *none* -
- -### Transactions pool +### Payment
Add transaction @@ -92,6 +204,17 @@ Each request value or response value shall be marshaled to bytes or un-marshaled * **response value:** Array of [transactions](#transaction)
+### Protocol +
+Get settings + +![/settings](https://img.shields.io/badge//settings-dimgray?style=flat-square) + +*Description:* Get protocol settings. +* **request value:** [settings](#settings) +* **response value:** *none* +
+ ### Wallet
Get UTXOs @@ -467,5 +590,3 @@ The value at the transaction timestamp - -[1]: https://go.dev/blog/gob "Gobs official documentation" diff --git a/validatornode/application/addresses_manager.go b/validatornode/application/addresses_manager.go new file mode 100644 index 00000000..b6cfd819 --- /dev/null +++ b/validatornode/application/addresses_manager.go @@ -0,0 +1,10 @@ +package application + +type AddressesManager interface { + Clear() + Copy() AddressesManager + Filter(addresses []string) (newAddresses []string) + IsRegistered(address string) bool + RemovedAddresses() (removedAddresses []string) + Update(addedAddresses []string, removedAddresses []string) +} diff --git a/validatornode/application/addresses_manager_mock.go b/validatornode/application/addresses_manager_mock.go new file mode 100644 index 00000000..1d998783 --- /dev/null +++ b/validatornode/application/addresses_manager_mock.go @@ -0,0 +1,279 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "sync" +) + +// Ensure, that AddressesManagerMock does implement AddressesManager. +// If this is not the case, regenerate this file with moq. +var _ AddressesManager = &AddressesManagerMock{} + +// AddressesManagerMock is a mock implementation of AddressesManager. +// +// func TestSomethingThatUsesAddressesManager(t *testing.T) { +// +// // make and configure a mocked AddressesManager +// mockedAddressesManager := &AddressesManagerMock{ +// ClearFunc: func() { +// panic("mock out the Clear method") +// }, +// CopyFunc: func() AddressesManager { +// panic("mock out the Copy method") +// }, +// FilterFunc: func(addresses []string) []string { +// panic("mock out the Filter method") +// }, +// IsRegisteredFunc: func(address string) bool { +// panic("mock out the IsRegistered method") +// }, +// RemovedAddressesFunc: func() []string { +// panic("mock out the RemovedAddresses method") +// }, +// UpdateFunc: func(addedAddresses []string, removedAddresses []string) { +// panic("mock out the Update method") +// }, +// } +// +// // use mockedAddressesManager in code that requires AddressesManager +// // and then make assertions. +// +// } +type AddressesManagerMock struct { + // ClearFunc mocks the Clear method. + ClearFunc func() + + // CopyFunc mocks the Copy method. + CopyFunc func() AddressesManager + + // FilterFunc mocks the Filter method. + FilterFunc func(addresses []string) []string + + // IsRegisteredFunc mocks the IsRegistered method. + IsRegisteredFunc func(address string) bool + + // RemovedAddressesFunc mocks the RemovedAddresses method. + RemovedAddressesFunc func() []string + + // UpdateFunc mocks the Update method. + UpdateFunc func(addedAddresses []string, removedAddresses []string) + + // calls tracks calls to the methods. + calls struct { + // Clear holds details about calls to the Clear method. + Clear []struct { + } + // Copy holds details about calls to the Copy method. + Copy []struct { + } + // Filter holds details about calls to the Filter method. + Filter []struct { + // Addresses is the addresses argument value. + Addresses []string + } + // IsRegistered holds details about calls to the IsRegistered method. + IsRegistered []struct { + // Address is the address argument value. + Address string + } + // RemovedAddresses holds details about calls to the RemovedAddresses method. + RemovedAddresses []struct { + } + // Update holds details about calls to the Update method. + Update []struct { + // AddedAddresses is the addedAddresses argument value. + AddedAddresses []string + // RemovedAddresses is the removedAddresses argument value. + RemovedAddresses []string + } + } + lockClear sync.RWMutex + lockCopy sync.RWMutex + lockFilter sync.RWMutex + lockIsRegistered sync.RWMutex + lockRemovedAddresses sync.RWMutex + lockUpdate sync.RWMutex +} + +// Clear calls ClearFunc. +func (mock *AddressesManagerMock) Clear() { + if mock.ClearFunc == nil { + panic("AddressesManagerMock.ClearFunc: method is nil but AddressesManager.Clear was just called") + } + callInfo := struct { + }{} + mock.lockClear.Lock() + mock.calls.Clear = append(mock.calls.Clear, callInfo) + mock.lockClear.Unlock() + mock.ClearFunc() +} + +// ClearCalls gets all the calls that were made to Clear. +// Check the length with: +// +// len(mockedAddressesManager.ClearCalls()) +func (mock *AddressesManagerMock) ClearCalls() []struct { +} { + var calls []struct { + } + mock.lockClear.RLock() + calls = mock.calls.Clear + mock.lockClear.RUnlock() + return calls +} + +// Copy calls CopyFunc. +func (mock *AddressesManagerMock) Copy() AddressesManager { + if mock.CopyFunc == nil { + panic("AddressesManagerMock.CopyFunc: method is nil but AddressesManager.Copy was just called") + } + callInfo := struct { + }{} + mock.lockCopy.Lock() + mock.calls.Copy = append(mock.calls.Copy, callInfo) + mock.lockCopy.Unlock() + return mock.CopyFunc() +} + +// CopyCalls gets all the calls that were made to Copy. +// Check the length with: +// +// len(mockedAddressesManager.CopyCalls()) +func (mock *AddressesManagerMock) CopyCalls() []struct { +} { + var calls []struct { + } + mock.lockCopy.RLock() + calls = mock.calls.Copy + mock.lockCopy.RUnlock() + return calls +} + +// Filter calls FilterFunc. +func (mock *AddressesManagerMock) Filter(addresses []string) []string { + if mock.FilterFunc == nil { + panic("AddressesManagerMock.FilterFunc: method is nil but AddressesManager.Filter was just called") + } + callInfo := struct { + Addresses []string + }{ + Addresses: addresses, + } + mock.lockFilter.Lock() + mock.calls.Filter = append(mock.calls.Filter, callInfo) + mock.lockFilter.Unlock() + return mock.FilterFunc(addresses) +} + +// FilterCalls gets all the calls that were made to Filter. +// Check the length with: +// +// len(mockedAddressesManager.FilterCalls()) +func (mock *AddressesManagerMock) FilterCalls() []struct { + Addresses []string +} { + var calls []struct { + Addresses []string + } + mock.lockFilter.RLock() + calls = mock.calls.Filter + mock.lockFilter.RUnlock() + return calls +} + +// IsRegistered calls IsRegisteredFunc. +func (mock *AddressesManagerMock) IsRegistered(address string) bool { + if mock.IsRegisteredFunc == nil { + panic("AddressesManagerMock.IsRegisteredFunc: method is nil but AddressesManager.IsRegistered was just called") + } + callInfo := struct { + Address string + }{ + Address: address, + } + mock.lockIsRegistered.Lock() + mock.calls.IsRegistered = append(mock.calls.IsRegistered, callInfo) + mock.lockIsRegistered.Unlock() + return mock.IsRegisteredFunc(address) +} + +// IsRegisteredCalls gets all the calls that were made to IsRegistered. +// Check the length with: +// +// len(mockedAddressesManager.IsRegisteredCalls()) +func (mock *AddressesManagerMock) IsRegisteredCalls() []struct { + Address string +} { + var calls []struct { + Address string + } + mock.lockIsRegistered.RLock() + calls = mock.calls.IsRegistered + mock.lockIsRegistered.RUnlock() + return calls +} + +// RemovedAddresses calls RemovedAddressesFunc. +func (mock *AddressesManagerMock) RemovedAddresses() []string { + if mock.RemovedAddressesFunc == nil { + panic("AddressesManagerMock.RemovedAddressesFunc: method is nil but AddressesManager.RemovedAddresses was just called") + } + callInfo := struct { + }{} + mock.lockRemovedAddresses.Lock() + mock.calls.RemovedAddresses = append(mock.calls.RemovedAddresses, callInfo) + mock.lockRemovedAddresses.Unlock() + return mock.RemovedAddressesFunc() +} + +// RemovedAddressesCalls gets all the calls that were made to RemovedAddresses. +// Check the length with: +// +// len(mockedAddressesManager.RemovedAddressesCalls()) +func (mock *AddressesManagerMock) RemovedAddressesCalls() []struct { +} { + var calls []struct { + } + mock.lockRemovedAddresses.RLock() + calls = mock.calls.RemovedAddresses + mock.lockRemovedAddresses.RUnlock() + return calls +} + +// Update calls UpdateFunc. +func (mock *AddressesManagerMock) Update(addedAddresses []string, removedAddresses []string) { + if mock.UpdateFunc == nil { + panic("AddressesManagerMock.UpdateFunc: method is nil but AddressesManager.Update was just called") + } + callInfo := struct { + AddedAddresses []string + RemovedAddresses []string + }{ + AddedAddresses: addedAddresses, + RemovedAddresses: removedAddresses, + } + mock.lockUpdate.Lock() + mock.calls.Update = append(mock.calls.Update, callInfo) + mock.lockUpdate.Unlock() + mock.UpdateFunc(addedAddresses, removedAddresses) +} + +// UpdateCalls gets all the calls that were made to Update. +// Check the length with: +// +// len(mockedAddressesManager.UpdateCalls()) +func (mock *AddressesManagerMock) UpdateCalls() []struct { + AddedAddresses []string + RemovedAddresses []string +} { + var calls []struct { + AddedAddresses []string + RemovedAddresses []string + } + mock.lockUpdate.RLock() + calls = mock.calls.Update + mock.lockUpdate.RUnlock() + return calls +} diff --git a/validatornode/application/blocks_manager.go b/validatornode/application/blocks_manager.go new file mode 100644 index 00000000..0d242505 --- /dev/null +++ b/validatornode/application/blocks_manager.go @@ -0,0 +1,11 @@ +package application + +import "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + +type BlocksManager interface { + AddBlock(timestamp int64, transactions []*ledger.Transaction, newRegisteredAddresses []string) error + Blocks(startingBlockHeight uint64) []*ledger.Block + FirstBlockTimestamp() int64 + LastBlockTimestamp() int64 + LastBlockTransactions() []*ledger.Transaction +} diff --git a/validatornode/application/blocks_manager_mock.go b/validatornode/application/blocks_manager_mock.go new file mode 100644 index 00000000..e4237011 --- /dev/null +++ b/validatornode/application/blocks_manager_mock.go @@ -0,0 +1,242 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "sync" +) + +// Ensure, that BlocksManagerMock does implement BlocksManager. +// If this is not the case, regenerate this file with moq. +var _ BlocksManager = &BlocksManagerMock{} + +// BlocksManagerMock is a mock implementation of BlocksManager. +// +// func TestSomethingThatUsesBlocksManager(t *testing.T) { +// +// // make and configure a mocked BlocksManager +// mockedBlocksManager := &BlocksManagerMock{ +// AddBlockFunc: func(timestamp int64, transactions []*ledger.Transaction, newRegisteredAddresses []string) error { +// panic("mock out the AddBlock method") +// }, +// BlocksFunc: func(startingBlockHeight uint64) []*ledger.Block { +// panic("mock out the Blocks method") +// }, +// FirstBlockTimestampFunc: func() int64 { +// panic("mock out the FirstBlockTimestamp method") +// }, +// LastBlockTimestampFunc: func() int64 { +// panic("mock out the LastBlockTimestamp method") +// }, +// LastBlockTransactionsFunc: func() []*ledger.Transaction { +// panic("mock out the LastBlockTransactions method") +// }, +// } +// +// // use mockedBlocksManager in code that requires BlocksManager +// // and then make assertions. +// +// } +type BlocksManagerMock struct { + // AddBlockFunc mocks the AddBlock method. + AddBlockFunc func(timestamp int64, transactions []*ledger.Transaction, newRegisteredAddresses []string) error + + // BlocksFunc mocks the Blocks method. + BlocksFunc func(startingBlockHeight uint64) []*ledger.Block + + // FirstBlockTimestampFunc mocks the FirstBlockTimestamp method. + FirstBlockTimestampFunc func() int64 + + // LastBlockTimestampFunc mocks the LastBlockTimestamp method. + LastBlockTimestampFunc func() int64 + + // LastBlockTransactionsFunc mocks the LastBlockTransactions method. + LastBlockTransactionsFunc func() []*ledger.Transaction + + // calls tracks calls to the methods. + calls struct { + // AddBlock holds details about calls to the AddBlock method. + AddBlock []struct { + // Timestamp is the timestamp argument value. + Timestamp int64 + // Transactions is the transactions argument value. + Transactions []*ledger.Transaction + // NewRegisteredAddresses is the newRegisteredAddresses argument value. + NewRegisteredAddresses []string + } + // Blocks holds details about calls to the Blocks method. + Blocks []struct { + // StartingBlockHeight is the startingBlockHeight argument value. + StartingBlockHeight uint64 + } + // FirstBlockTimestamp holds details about calls to the FirstBlockTimestamp method. + FirstBlockTimestamp []struct { + } + // LastBlockTimestamp holds details about calls to the LastBlockTimestamp method. + LastBlockTimestamp []struct { + } + // LastBlockTransactions holds details about calls to the LastBlockTransactions method. + LastBlockTransactions []struct { + } + } + lockAddBlock sync.RWMutex + lockBlocks sync.RWMutex + lockFirstBlockTimestamp sync.RWMutex + lockLastBlockTimestamp sync.RWMutex + lockLastBlockTransactions sync.RWMutex +} + +// AddBlock calls AddBlockFunc. +func (mock *BlocksManagerMock) AddBlock(timestamp int64, transactions []*ledger.Transaction, newRegisteredAddresses []string) error { + if mock.AddBlockFunc == nil { + panic("BlocksManagerMock.AddBlockFunc: method is nil but BlocksManager.AddBlock was just called") + } + callInfo := struct { + Timestamp int64 + Transactions []*ledger.Transaction + NewRegisteredAddresses []string + }{ + Timestamp: timestamp, + Transactions: transactions, + NewRegisteredAddresses: newRegisteredAddresses, + } + mock.lockAddBlock.Lock() + mock.calls.AddBlock = append(mock.calls.AddBlock, callInfo) + mock.lockAddBlock.Unlock() + return mock.AddBlockFunc(timestamp, transactions, newRegisteredAddresses) +} + +// AddBlockCalls gets all the calls that were made to AddBlock. +// Check the length with: +// +// len(mockedBlocksManager.AddBlockCalls()) +func (mock *BlocksManagerMock) AddBlockCalls() []struct { + Timestamp int64 + Transactions []*ledger.Transaction + NewRegisteredAddresses []string +} { + var calls []struct { + Timestamp int64 + Transactions []*ledger.Transaction + NewRegisteredAddresses []string + } + mock.lockAddBlock.RLock() + calls = mock.calls.AddBlock + mock.lockAddBlock.RUnlock() + return calls +} + +// Blocks calls BlocksFunc. +func (mock *BlocksManagerMock) Blocks(startingBlockHeight uint64) []*ledger.Block { + if mock.BlocksFunc == nil { + panic("BlocksManagerMock.BlocksFunc: method is nil but BlocksManager.Blocks was just called") + } + callInfo := struct { + StartingBlockHeight uint64 + }{ + StartingBlockHeight: startingBlockHeight, + } + mock.lockBlocks.Lock() + mock.calls.Blocks = append(mock.calls.Blocks, callInfo) + mock.lockBlocks.Unlock() + return mock.BlocksFunc(startingBlockHeight) +} + +// BlocksCalls gets all the calls that were made to Blocks. +// Check the length with: +// +// len(mockedBlocksManager.BlocksCalls()) +func (mock *BlocksManagerMock) BlocksCalls() []struct { + StartingBlockHeight uint64 +} { + var calls []struct { + StartingBlockHeight uint64 + } + mock.lockBlocks.RLock() + calls = mock.calls.Blocks + mock.lockBlocks.RUnlock() + return calls +} + +// FirstBlockTimestamp calls FirstBlockTimestampFunc. +func (mock *BlocksManagerMock) FirstBlockTimestamp() int64 { + if mock.FirstBlockTimestampFunc == nil { + panic("BlocksManagerMock.FirstBlockTimestampFunc: method is nil but BlocksManager.FirstBlockTimestamp was just called") + } + callInfo := struct { + }{} + mock.lockFirstBlockTimestamp.Lock() + mock.calls.FirstBlockTimestamp = append(mock.calls.FirstBlockTimestamp, callInfo) + mock.lockFirstBlockTimestamp.Unlock() + return mock.FirstBlockTimestampFunc() +} + +// FirstBlockTimestampCalls gets all the calls that were made to FirstBlockTimestamp. +// Check the length with: +// +// len(mockedBlocksManager.FirstBlockTimestampCalls()) +func (mock *BlocksManagerMock) FirstBlockTimestampCalls() []struct { +} { + var calls []struct { + } + mock.lockFirstBlockTimestamp.RLock() + calls = mock.calls.FirstBlockTimestamp + mock.lockFirstBlockTimestamp.RUnlock() + return calls +} + +// LastBlockTimestamp calls LastBlockTimestampFunc. +func (mock *BlocksManagerMock) LastBlockTimestamp() int64 { + if mock.LastBlockTimestampFunc == nil { + panic("BlocksManagerMock.LastBlockTimestampFunc: method is nil but BlocksManager.LastBlockTimestamp was just called") + } + callInfo := struct { + }{} + mock.lockLastBlockTimestamp.Lock() + mock.calls.LastBlockTimestamp = append(mock.calls.LastBlockTimestamp, callInfo) + mock.lockLastBlockTimestamp.Unlock() + return mock.LastBlockTimestampFunc() +} + +// LastBlockTimestampCalls gets all the calls that were made to LastBlockTimestamp. +// Check the length with: +// +// len(mockedBlocksManager.LastBlockTimestampCalls()) +func (mock *BlocksManagerMock) LastBlockTimestampCalls() []struct { +} { + var calls []struct { + } + mock.lockLastBlockTimestamp.RLock() + calls = mock.calls.LastBlockTimestamp + mock.lockLastBlockTimestamp.RUnlock() + return calls +} + +// LastBlockTransactions calls LastBlockTransactionsFunc. +func (mock *BlocksManagerMock) LastBlockTransactions() []*ledger.Transaction { + if mock.LastBlockTransactionsFunc == nil { + panic("BlocksManagerMock.LastBlockTransactionsFunc: method is nil but BlocksManager.LastBlockTransactions was just called") + } + callInfo := struct { + }{} + mock.lockLastBlockTransactions.Lock() + mock.calls.LastBlockTransactions = append(mock.calls.LastBlockTransactions, callInfo) + mock.lockLastBlockTransactions.Unlock() + return mock.LastBlockTransactionsFunc() +} + +// LastBlockTransactionsCalls gets all the calls that were made to LastBlockTransactions. +// Check the length with: +// +// len(mockedBlocksManager.LastBlockTransactionsCalls()) +func (mock *BlocksManagerMock) LastBlockTransactionsCalls() []struct { +} { + var calls []struct { + } + mock.lockLastBlockTransactions.RLock() + calls = mock.calls.LastBlockTransactions + mock.lockLastBlockTransactions.RUnlock() + return calls +} diff --git a/validatornode/application/network/neighborhood.go b/validatornode/application/network/neighborhood.go new file mode 100644 index 00000000..efa6a51e --- /dev/null +++ b/validatornode/application/network/neighborhood.go @@ -0,0 +1,139 @@ +package network + +import ( + "github.com/my-cloud/ruthenium/validatornode/application" + "math/rand" + "sort" + "sync" + "time" +) + +type Neighborhood struct { + senderCreator application.SenderCreator + hostTarget *Target + maxOutboundsCount int + senders []application.Sender + sendersMutex sync.RWMutex + scoresBySeedTargetValue map[string]int + scoresByTargetValue map[string]int + scoresByTargetValueMutex sync.RWMutex + watch application.TimeProvider +} + +func NewNeighborhood(senderCreator application.SenderCreator, hostIp string, hostPort string, maxOutboundsCount int, scoresBySeedTargetValue map[string]int, watch application.TimeProvider) *Neighborhood { + neighborhood := new(Neighborhood) + neighborhood.senderCreator = senderCreator + neighborhood.hostTarget = NewTarget(hostIp, hostPort) + neighborhood.maxOutboundsCount = maxOutboundsCount + neighborhood.scoresBySeedTargetValue = scoresBySeedTargetValue + neighborhood.scoresByTargetValue = map[string]int{} + neighborhood.watch = watch + return neighborhood +} + +func (neighborhood *Neighborhood) AddTargets(targetValues []string) { + neighborhood.scoresByTargetValueMutex.Lock() + defer neighborhood.scoresByTargetValueMutex.Unlock() + for _, targetValue := range targetValues { + _, isTargetAlreadyKnown := neighborhood.scoresByTargetValue[targetValue] + target, err := NewTargetFromValue(targetValue) + if err != nil { + continue + } + isTargetOnSameNetwork := neighborhood.hostTarget.IsSameNetworkId(target) + if !isTargetAlreadyKnown && isTargetOnSameNetwork { + neighborhood.scoresByTargetValue[targetValue] = 0 + } + } +} + +func (neighborhood *Neighborhood) HostTarget() string { + return neighborhood.hostTarget.Value() +} + +func (neighborhood *Neighborhood) Incentive(targetValue string) { + neighborhood.scoresByTargetValueMutex.Lock() + defer neighborhood.scoresByTargetValueMutex.Unlock() + neighborhood.scoresByTargetValue[targetValue] += 1 +} + +func (neighborhood *Neighborhood) Senders() []application.Sender { + return neighborhood.senders +} + +func (neighborhood *Neighborhood) Synchronize(_ int64) { + neighborhood.scoresByTargetValueMutex.Lock() + var scoresByTargetValue map[string]int + if len(neighborhood.scoresByTargetValue) == 0 { + scoresByTargetValue = neighborhood.scoresBySeedTargetValue + } else { + scoresByTargetValue = neighborhood.scoresByTargetValue + } + neighborhood.scoresByTargetValue = map[string]int{} + neighborhood.scoresByTargetValueMutex.Unlock() + neighborsByScore := map[int][]application.Sender{} + var targetValues []string + hostTargetValue := neighborhood.hostTarget.Value() + targetValues = append(targetValues, hostTargetValue) + for targetValue, score := range scoresByTargetValue { + if targetValue != hostTargetValue { + neighborTarget, err := NewTargetFromValue(targetValue) + if err != nil { + continue + } + neighbor, err := neighborhood.senderCreator.CreateSender(neighborTarget.Ip(), neighborTarget.Port()) + if err != nil { + continue + } + neighborsByScore[score] = append(neighborsByScore[score], neighbor) + targetValues = append(targetValues, targetValue) + } + } + outbounds := neighborhood.selectOutbounds(neighborsByScore, len(scoresByTargetValue)) + neighborhood.sendersMutex.Lock() + neighborhood.senders = outbounds + neighborhood.sendersMutex.Unlock() + for _, neighbor := range outbounds { + var neighborTargetValues []string + for _, targetValue := range targetValues { + neighborTargetValue := neighbor.Target() + if neighborTargetValue != targetValue { + neighborTargetValues = append(neighborTargetValues, targetValue) + } + } + go func(neighbor application.Sender) { + _ = neighbor.SendTargets(neighborTargetValues) + }(neighbor) + } +} + +func (neighborhood *Neighborhood) selectOutbounds(neighborsByScore map[int][]application.Sender, targetsCount int) []application.Sender { + var keys []int + for k := range neighborsByScore { + keys = append(keys, k) + } + sort.Ints(keys) + outboundsCount := min(targetsCount, neighborhood.maxOutboundsCount) + var outbounds []application.Sender + for i := len(keys) - 1; i >= 0; i-- { + if len(outbounds)+len(neighborsByScore[keys[i]]) >= outboundsCount { + temp := neighborsByScore[keys[i]] + rand.Seed(time.Now().UnixNano()) + rand.Shuffle(len(temp), func(i, j int) { temp[i], temp[j] = temp[j], temp[i] }) + outbounds = append(outbounds, temp[:outboundsCount-len(outbounds)]...) + break + } + outbounds = append(outbounds, neighborsByScore[keys[i]]...) + } + return outbounds +} + +func min(first, second int) int { + var result int + if first < second { + result = first + } else { + result = second + } + return result +} diff --git a/validatornode/application/network/neighborhood_test.go b/validatornode/application/network/neighborhood_test.go new file mode 100644 index 00000000..ae5da6d7 --- /dev/null +++ b/validatornode/application/network/neighborhood_test.go @@ -0,0 +1,83 @@ +package network + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_AddTargets_MoreThanOneTarget_IncentiveTargetsSender(t *testing.T) { + // Arrange + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Now() } + senderCreatorMock := new(application.SenderCreatorMock) + senderMock := new(application.SenderMock) + senderMock.TargetFunc = func() string { return "0.0.0.0:1" } + senderMock.SendTargetsFunc = func([]string) error { return nil } + senderCreatorMock.CreateSenderFunc = func(string, string) (application.Sender, error) { return senderMock, nil } + scoresBySeedTarget := map[string]int{} + neighborhood := NewNeighborhood(senderCreatorMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) + target1 := "0.0.0.0:1" + target2 := "0.0.0.0:0" + targetRequests := []string{target1, target2} + + // Act + neighborhood.AddTargets(targetRequests) + + // Assert + neighborhood.Synchronize(0) + neighbors := neighborhood.Senders() + expectedNeighborsCount := 1 + test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) + neighborTarget := neighbors[0].Target() + test.Assert(t, neighborTarget == target1, fmt.Sprintf("Wrong neighbor. Expected: %s - Actual: %s", target1, neighborTarget)) +} + +func Test_Incentive_TargetIsNotKnown_TargetIncentive(t *testing.T) { + // Arrange + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Now() } + senderCreatorMock := new(application.SenderCreatorMock) + senderMock := new(application.SenderMock) + senderMock.TargetFunc = func() string { return "0.0.0.0:1" } + senderMock.SendTargetsFunc = func([]string) error { return nil } + senderCreatorMock.CreateSenderFunc = func(string, string) (application.Sender, error) { return senderMock, nil } + scoresBySeedTarget := map[string]int{} + neighborhood := NewNeighborhood(senderCreatorMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) + expectedTarget := "0.0.0.0:1" + + // Act + neighborhood.Incentive(expectedTarget) + + // Assert + neighborhood.Synchronize(0) + neighbors := neighborhood.Senders() + expectedNeighborsCount := 1 + test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) + target := neighbors[0].Target() + test.Assert(t, target == expectedTarget, fmt.Sprintf("Wrong target. Expected: %s - Actual: %s", expectedTarget, target)) +} + +func Test_Synchronize_OneSeed_NeighborAdded(t *testing.T) { + // Arrange + watchMock := new(application.TimeProviderMock) + watchMock.NowFunc = func() time.Time { return time.Now() } + senderCreatorMock := new(application.SenderCreatorMock) + senderMock := new(application.SenderMock) + senderMock.TargetFunc = func() string { return "0.0.0.0:1" } + senderMock.SendTargetsFunc = func([]string) error { return nil } + senderCreatorMock.CreateSenderFunc = func(string, string) (application.Sender, error) { return senderMock, nil } + scoresBySeedTarget := map[string]int{"0.0.0.0:1": 0} + neighborhood := NewNeighborhood(senderCreatorMock, "0.0.0.0", "0", 1, scoresBySeedTarget, watchMock) + + // Act + neighborhood.Synchronize(0) + + // Assert + neighbors := neighborhood.Senders() + expectedNeighborsCount := 1 + test.Assert(t, len(neighbors) == expectedNeighborsCount, fmt.Sprintf("Wrong neighbors count. Expected: %d - Actual: %d", expectedNeighborsCount, len(neighbors))) +} diff --git a/src/node/network/p2p/target.go b/validatornode/application/network/target.go similarity index 98% rename from src/node/network/p2p/target.go rename to validatornode/application/network/target.go index f14c40be..fc1c8bcf 100644 --- a/src/node/network/p2p/target.go +++ b/validatornode/application/network/target.go @@ -1,4 +1,4 @@ -package p2p +package network import ( "fmt" diff --git a/src/node/protocol/settings.go b/validatornode/application/protocol_settings_provider.go similarity index 59% rename from src/node/protocol/settings.go rename to validatornode/application/protocol_settings_provider.go index 9c1a5d7d..80b5a265 100644 --- a/src/node/protocol/settings.go +++ b/validatornode/application/protocol_settings_provider.go @@ -1,14 +1,17 @@ -package protocol +package application import "time" -type Settings interface { +type ProtocolSettingsProvider interface { BlocksCountLimit() uint64 GenesisAmount() uint64 HalfLifeInNanoseconds() float64 IncomeBase() uint64 IncomeLimit() uint64 MinimalTransactionFee() uint64 + SmallestUnitsPerCoin() uint64 ValidationTimeout() time.Duration + ValidationTimer() time.Duration ValidationTimestamp() int64 + VerificationsCountPerValidation() int64 } diff --git a/validatornode/application/protocol_settings_provider_mock.go b/validatornode/application/protocol_settings_provider_mock.go new file mode 100644 index 00000000..74d69ea8 --- /dev/null +++ b/validatornode/application/protocol_settings_provider_mock.go @@ -0,0 +1,438 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "sync" + "time" +) + +// Ensure, that ProtocolSettingsProviderMock does implement ProtocolSettingsProvider. +// If this is not the case, regenerate this file with moq. +var _ ProtocolSettingsProvider = &ProtocolSettingsProviderMock{} + +// ProtocolSettingsProviderMock is a mock implementation of ProtocolSettingsProvider. +// +// func TestSomethingThatUsesProtocolSettingsProvider(t *testing.T) { +// +// // make and configure a mocked ProtocolSettingsProvider +// mockedProtocolSettingsProvider := &ProtocolSettingsProviderMock{ +// BlocksCountLimitFunc: func() uint64 { +// panic("mock out the BlocksCountLimit method") +// }, +// GenesisAmountFunc: func() uint64 { +// panic("mock out the GenesisAmount method") +// }, +// HalfLifeInNanosecondsFunc: func() float64 { +// panic("mock out the HalfLifeInNanoseconds method") +// }, +// IncomeBaseFunc: func() uint64 { +// panic("mock out the IncomeBase method") +// }, +// IncomeLimitFunc: func() uint64 { +// panic("mock out the IncomeLimit method") +// }, +// MinimalTransactionFeeFunc: func() uint64 { +// panic("mock out the MinimalTransactionFee method") +// }, +// SmallestUnitsPerCoinFunc: func() uint64 { +// panic("mock out the SmallestUnitsPerCoin method") +// }, +// ValidationTimeoutFunc: func() time.Duration { +// panic("mock out the ValidationTimeout method") +// }, +// ValidationTimerFunc: func() time.Duration { +// panic("mock out the ValidationTimer method") +// }, +// ValidationTimestampFunc: func() int64 { +// panic("mock out the ValidationTimestamp method") +// }, +// VerificationsCountPerValidationFunc: func() int64 { +// panic("mock out the VerificationsCountPerValidation method") +// }, +// } +// +// // use mockedProtocolSettingsProvider in code that requires ProtocolSettingsProvider +// // and then make assertions. +// +// } +type ProtocolSettingsProviderMock struct { + // BlocksCountLimitFunc mocks the BlocksCountLimit method. + BlocksCountLimitFunc func() uint64 + + // GenesisAmountFunc mocks the GenesisAmount method. + GenesisAmountFunc func() uint64 + + // HalfLifeInNanosecondsFunc mocks the HalfLifeInNanoseconds method. + HalfLifeInNanosecondsFunc func() float64 + + // IncomeBaseFunc mocks the IncomeBase method. + IncomeBaseFunc func() uint64 + + // IncomeLimitFunc mocks the IncomeLimit method. + IncomeLimitFunc func() uint64 + + // MinimalTransactionFeeFunc mocks the MinimalTransactionFee method. + MinimalTransactionFeeFunc func() uint64 + + // SmallestUnitsPerCoinFunc mocks the SmallestUnitsPerCoin method. + SmallestUnitsPerCoinFunc func() uint64 + + // ValidationTimeoutFunc mocks the ValidationTimeout method. + ValidationTimeoutFunc func() time.Duration + + // ValidationTimerFunc mocks the ValidationTimer method. + ValidationTimerFunc func() time.Duration + + // ValidationTimestampFunc mocks the ValidationTimestamp method. + ValidationTimestampFunc func() int64 + + // VerificationsCountPerValidationFunc mocks the VerificationsCountPerValidation method. + VerificationsCountPerValidationFunc func() int64 + + // calls tracks calls to the methods. + calls struct { + // BlocksCountLimit holds details about calls to the BlocksCountLimit method. + BlocksCountLimit []struct { + } + // GenesisAmount holds details about calls to the GenesisAmount method. + GenesisAmount []struct { + } + // HalfLifeInNanoseconds holds details about calls to the HalfLifeInNanoseconds method. + HalfLifeInNanoseconds []struct { + } + // IncomeBase holds details about calls to the IncomeBase method. + IncomeBase []struct { + } + // IncomeLimit holds details about calls to the IncomeLimit method. + IncomeLimit []struct { + } + // MinimalTransactionFee holds details about calls to the MinimalTransactionFee method. + MinimalTransactionFee []struct { + } + // SmallestUnitsPerCoin holds details about calls to the SmallestUnitsPerCoin method. + SmallestUnitsPerCoin []struct { + } + // ValidationTimeout holds details about calls to the ValidationTimeout method. + ValidationTimeout []struct { + } + // ValidationTimer holds details about calls to the ValidationTimer method. + ValidationTimer []struct { + } + // ValidationTimestamp holds details about calls to the ValidationTimestamp method. + ValidationTimestamp []struct { + } + // VerificationsCountPerValidation holds details about calls to the VerificationsCountPerValidation method. + VerificationsCountPerValidation []struct { + } + } + lockBlocksCountLimit sync.RWMutex + lockGenesisAmount sync.RWMutex + lockHalfLifeInNanoseconds sync.RWMutex + lockIncomeBase sync.RWMutex + lockIncomeLimit sync.RWMutex + lockMinimalTransactionFee sync.RWMutex + lockSmallestUnitsPerCoin sync.RWMutex + lockValidationTimeout sync.RWMutex + lockValidationTimer sync.RWMutex + lockValidationTimestamp sync.RWMutex + lockVerificationsCountPerValidation sync.RWMutex +} + +// BlocksCountLimit calls BlocksCountLimitFunc. +func (mock *ProtocolSettingsProviderMock) BlocksCountLimit() uint64 { + if mock.BlocksCountLimitFunc == nil { + panic("ProtocolSettingsProviderMock.BlocksCountLimitFunc: method is nil but ProtocolSettingsProvider.BlocksCountLimit was just called") + } + callInfo := struct { + }{} + mock.lockBlocksCountLimit.Lock() + mock.calls.BlocksCountLimit = append(mock.calls.BlocksCountLimit, callInfo) + mock.lockBlocksCountLimit.Unlock() + return mock.BlocksCountLimitFunc() +} + +// BlocksCountLimitCalls gets all the calls that were made to BlocksCountLimit. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.BlocksCountLimitCalls()) +func (mock *ProtocolSettingsProviderMock) BlocksCountLimitCalls() []struct { +} { + var calls []struct { + } + mock.lockBlocksCountLimit.RLock() + calls = mock.calls.BlocksCountLimit + mock.lockBlocksCountLimit.RUnlock() + return calls +} + +// GenesisAmount calls GenesisAmountFunc. +func (mock *ProtocolSettingsProviderMock) GenesisAmount() uint64 { + if mock.GenesisAmountFunc == nil { + panic("ProtocolSettingsProviderMock.GenesisAmountFunc: method is nil but ProtocolSettingsProvider.GenesisAmount was just called") + } + callInfo := struct { + }{} + mock.lockGenesisAmount.Lock() + mock.calls.GenesisAmount = append(mock.calls.GenesisAmount, callInfo) + mock.lockGenesisAmount.Unlock() + return mock.GenesisAmountFunc() +} + +// GenesisAmountCalls gets all the calls that were made to GenesisAmount. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.GenesisAmountCalls()) +func (mock *ProtocolSettingsProviderMock) GenesisAmountCalls() []struct { +} { + var calls []struct { + } + mock.lockGenesisAmount.RLock() + calls = mock.calls.GenesisAmount + mock.lockGenesisAmount.RUnlock() + return calls +} + +// HalfLifeInNanoseconds calls HalfLifeInNanosecondsFunc. +func (mock *ProtocolSettingsProviderMock) HalfLifeInNanoseconds() float64 { + if mock.HalfLifeInNanosecondsFunc == nil { + panic("ProtocolSettingsProviderMock.HalfLifeInNanosecondsFunc: method is nil but ProtocolSettingsProvider.HalfLifeInNanoseconds was just called") + } + callInfo := struct { + }{} + mock.lockHalfLifeInNanoseconds.Lock() + mock.calls.HalfLifeInNanoseconds = append(mock.calls.HalfLifeInNanoseconds, callInfo) + mock.lockHalfLifeInNanoseconds.Unlock() + return mock.HalfLifeInNanosecondsFunc() +} + +// HalfLifeInNanosecondsCalls gets all the calls that were made to HalfLifeInNanoseconds. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.HalfLifeInNanosecondsCalls()) +func (mock *ProtocolSettingsProviderMock) HalfLifeInNanosecondsCalls() []struct { +} { + var calls []struct { + } + mock.lockHalfLifeInNanoseconds.RLock() + calls = mock.calls.HalfLifeInNanoseconds + mock.lockHalfLifeInNanoseconds.RUnlock() + return calls +} + +// IncomeBase calls IncomeBaseFunc. +func (mock *ProtocolSettingsProviderMock) IncomeBase() uint64 { + if mock.IncomeBaseFunc == nil { + panic("ProtocolSettingsProviderMock.IncomeBaseFunc: method is nil but ProtocolSettingsProvider.IncomeBase was just called") + } + callInfo := struct { + }{} + mock.lockIncomeBase.Lock() + mock.calls.IncomeBase = append(mock.calls.IncomeBase, callInfo) + mock.lockIncomeBase.Unlock() + return mock.IncomeBaseFunc() +} + +// IncomeBaseCalls gets all the calls that were made to IncomeBase. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.IncomeBaseCalls()) +func (mock *ProtocolSettingsProviderMock) IncomeBaseCalls() []struct { +} { + var calls []struct { + } + mock.lockIncomeBase.RLock() + calls = mock.calls.IncomeBase + mock.lockIncomeBase.RUnlock() + return calls +} + +// IncomeLimit calls IncomeLimitFunc. +func (mock *ProtocolSettingsProviderMock) IncomeLimit() uint64 { + if mock.IncomeLimitFunc == nil { + panic("ProtocolSettingsProviderMock.IncomeLimitFunc: method is nil but ProtocolSettingsProvider.IncomeLimit was just called") + } + callInfo := struct { + }{} + mock.lockIncomeLimit.Lock() + mock.calls.IncomeLimit = append(mock.calls.IncomeLimit, callInfo) + mock.lockIncomeLimit.Unlock() + return mock.IncomeLimitFunc() +} + +// IncomeLimitCalls gets all the calls that were made to IncomeLimit. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.IncomeLimitCalls()) +func (mock *ProtocolSettingsProviderMock) IncomeLimitCalls() []struct { +} { + var calls []struct { + } + mock.lockIncomeLimit.RLock() + calls = mock.calls.IncomeLimit + mock.lockIncomeLimit.RUnlock() + return calls +} + +// MinimalTransactionFee calls MinimalTransactionFeeFunc. +func (mock *ProtocolSettingsProviderMock) MinimalTransactionFee() uint64 { + if mock.MinimalTransactionFeeFunc == nil { + panic("ProtocolSettingsProviderMock.MinimalTransactionFeeFunc: method is nil but ProtocolSettingsProvider.MinimalTransactionFee was just called") + } + callInfo := struct { + }{} + mock.lockMinimalTransactionFee.Lock() + mock.calls.MinimalTransactionFee = append(mock.calls.MinimalTransactionFee, callInfo) + mock.lockMinimalTransactionFee.Unlock() + return mock.MinimalTransactionFeeFunc() +} + +// MinimalTransactionFeeCalls gets all the calls that were made to MinimalTransactionFee. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.MinimalTransactionFeeCalls()) +func (mock *ProtocolSettingsProviderMock) MinimalTransactionFeeCalls() []struct { +} { + var calls []struct { + } + mock.lockMinimalTransactionFee.RLock() + calls = mock.calls.MinimalTransactionFee + mock.lockMinimalTransactionFee.RUnlock() + return calls +} + +// SmallestUnitsPerCoin calls SmallestUnitsPerCoinFunc. +func (mock *ProtocolSettingsProviderMock) SmallestUnitsPerCoin() uint64 { + if mock.SmallestUnitsPerCoinFunc == nil { + panic("ProtocolSettingsProviderMock.SmallestUnitsPerCoinFunc: method is nil but ProtocolSettingsProvider.SmallestUnitsPerCoin was just called") + } + callInfo := struct { + }{} + mock.lockSmallestUnitsPerCoin.Lock() + mock.calls.SmallestUnitsPerCoin = append(mock.calls.SmallestUnitsPerCoin, callInfo) + mock.lockSmallestUnitsPerCoin.Unlock() + return mock.SmallestUnitsPerCoinFunc() +} + +// SmallestUnitsPerCoinCalls gets all the calls that were made to SmallestUnitsPerCoin. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.SmallestUnitsPerCoinCalls()) +func (mock *ProtocolSettingsProviderMock) SmallestUnitsPerCoinCalls() []struct { +} { + var calls []struct { + } + mock.lockSmallestUnitsPerCoin.RLock() + calls = mock.calls.SmallestUnitsPerCoin + mock.lockSmallestUnitsPerCoin.RUnlock() + return calls +} + +// ValidationTimeout calls ValidationTimeoutFunc. +func (mock *ProtocolSettingsProviderMock) ValidationTimeout() time.Duration { + if mock.ValidationTimeoutFunc == nil { + panic("ProtocolSettingsProviderMock.ValidationTimeoutFunc: method is nil but ProtocolSettingsProvider.ValidationTimeout was just called") + } + callInfo := struct { + }{} + mock.lockValidationTimeout.Lock() + mock.calls.ValidationTimeout = append(mock.calls.ValidationTimeout, callInfo) + mock.lockValidationTimeout.Unlock() + return mock.ValidationTimeoutFunc() +} + +// ValidationTimeoutCalls gets all the calls that were made to ValidationTimeout. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.ValidationTimeoutCalls()) +func (mock *ProtocolSettingsProviderMock) ValidationTimeoutCalls() []struct { +} { + var calls []struct { + } + mock.lockValidationTimeout.RLock() + calls = mock.calls.ValidationTimeout + mock.lockValidationTimeout.RUnlock() + return calls +} + +// ValidationTimer calls ValidationTimerFunc. +func (mock *ProtocolSettingsProviderMock) ValidationTimer() time.Duration { + if mock.ValidationTimerFunc == nil { + panic("ProtocolSettingsProviderMock.ValidationTimerFunc: method is nil but ProtocolSettingsProvider.ValidationTimer was just called") + } + callInfo := struct { + }{} + mock.lockValidationTimer.Lock() + mock.calls.ValidationTimer = append(mock.calls.ValidationTimer, callInfo) + mock.lockValidationTimer.Unlock() + return mock.ValidationTimerFunc() +} + +// ValidationTimerCalls gets all the calls that were made to ValidationTimer. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.ValidationTimerCalls()) +func (mock *ProtocolSettingsProviderMock) ValidationTimerCalls() []struct { +} { + var calls []struct { + } + mock.lockValidationTimer.RLock() + calls = mock.calls.ValidationTimer + mock.lockValidationTimer.RUnlock() + return calls +} + +// ValidationTimestamp calls ValidationTimestampFunc. +func (mock *ProtocolSettingsProviderMock) ValidationTimestamp() int64 { + if mock.ValidationTimestampFunc == nil { + panic("ProtocolSettingsProviderMock.ValidationTimestampFunc: method is nil but ProtocolSettingsProvider.ValidationTimestamp was just called") + } + callInfo := struct { + }{} + mock.lockValidationTimestamp.Lock() + mock.calls.ValidationTimestamp = append(mock.calls.ValidationTimestamp, callInfo) + mock.lockValidationTimestamp.Unlock() + return mock.ValidationTimestampFunc() +} + +// ValidationTimestampCalls gets all the calls that were made to ValidationTimestamp. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.ValidationTimestampCalls()) +func (mock *ProtocolSettingsProviderMock) ValidationTimestampCalls() []struct { +} { + var calls []struct { + } + mock.lockValidationTimestamp.RLock() + calls = mock.calls.ValidationTimestamp + mock.lockValidationTimestamp.RUnlock() + return calls +} + +// VerificationsCountPerValidation calls VerificationsCountPerValidationFunc. +func (mock *ProtocolSettingsProviderMock) VerificationsCountPerValidation() int64 { + if mock.VerificationsCountPerValidationFunc == nil { + panic("ProtocolSettingsProviderMock.VerificationsCountPerValidationFunc: method is nil but ProtocolSettingsProvider.VerificationsCountPerValidation was just called") + } + callInfo := struct { + }{} + mock.lockVerificationsCountPerValidation.Lock() + mock.calls.VerificationsCountPerValidation = append(mock.calls.VerificationsCountPerValidation, callInfo) + mock.lockVerificationsCountPerValidation.Unlock() + return mock.VerificationsCountPerValidationFunc() +} + +// VerificationsCountPerValidationCalls gets all the calls that were made to VerificationsCountPerValidation. +// Check the length with: +// +// len(mockedProtocolSettingsProvider.VerificationsCountPerValidationCalls()) +func (mock *ProtocolSettingsProviderMock) VerificationsCountPerValidationCalls() []struct { +} { + var calls []struct { + } + mock.lockVerificationsCountPerValidation.RLock() + calls = mock.calls.VerificationsCountPerValidation + mock.lockVerificationsCountPerValidation.RUnlock() + return calls +} diff --git a/src/node/network/neighbor.go b/validatornode/application/sender.go similarity index 89% rename from src/node/network/neighbor.go rename to validatornode/application/sender.go index b49dd0df..d98b3f86 100644 --- a/src/node/network/neighbor.go +++ b/validatornode/application/sender.go @@ -1,6 +1,6 @@ -package network +package application -type Neighbor interface { +type Sender interface { Target() string GetBlocks(startingBlockHeight uint64) (blocks []byte, err error) GetFirstBlockTimestamp() (firstBlockTimestamp int64, err error) diff --git a/validatornode/application/sender_creator.go b/validatornode/application/sender_creator.go new file mode 100644 index 00000000..488d8994 --- /dev/null +++ b/validatornode/application/sender_creator.go @@ -0,0 +1,5 @@ +package application + +type SenderCreator interface { + CreateSender(ip string, port string) (Sender, error) +} diff --git a/validatornode/application/sender_creator_mock.go b/validatornode/application/sender_creator_mock.go new file mode 100644 index 00000000..13fd2fdf --- /dev/null +++ b/validatornode/application/sender_creator_mock.go @@ -0,0 +1,80 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "sync" +) + +// Ensure, that SenderCreatorMock does implement SenderCreator. +// If this is not the case, regenerate this file with moq. +var _ SenderCreator = &SenderCreatorMock{} + +// SenderCreatorMock is a mock implementation of SenderCreator. +// +// func TestSomethingThatUsesSenderCreator(t *testing.T) { +// +// // make and configure a mocked SenderCreator +// mockedSenderCreator := &SenderCreatorMock{ +// CreateSenderFunc: func(ip string, port string) (Sender, error) { +// panic("mock out the CreateSender method") +// }, +// } +// +// // use mockedSenderCreator in code that requires SenderCreator +// // and then make assertions. +// +// } +type SenderCreatorMock struct { + // CreateSenderFunc mocks the CreateSender method. + CreateSenderFunc func(ip string, port string) (Sender, error) + + // calls tracks calls to the methods. + calls struct { + // CreateSender holds details about calls to the CreateSender method. + CreateSender []struct { + // IP is the ip argument value. + IP string + // Port is the port argument value. + Port string + } + } + lockCreateSender sync.RWMutex +} + +// CreateSender calls CreateSenderFunc. +func (mock *SenderCreatorMock) CreateSender(ip string, port string) (Sender, error) { + if mock.CreateSenderFunc == nil { + panic("SenderCreatorMock.CreateSenderFunc: method is nil but SenderCreator.CreateSender was just called") + } + callInfo := struct { + IP string + Port string + }{ + IP: ip, + Port: port, + } + mock.lockCreateSender.Lock() + mock.calls.CreateSender = append(mock.calls.CreateSender, callInfo) + mock.lockCreateSender.Unlock() + return mock.CreateSenderFunc(ip, port) +} + +// CreateSenderCalls gets all the calls that were made to CreateSender. +// Check the length with: +// +// len(mockedSenderCreator.CreateSenderCalls()) +func (mock *SenderCreatorMock) CreateSenderCalls() []struct { + IP string + Port string +} { + var calls []struct { + IP string + Port string + } + mock.lockCreateSender.RLock() + calls = mock.calls.CreateSender + mock.lockCreateSender.RUnlock() + return calls +} diff --git a/test/node/network/networktest/neighbor_mock.go b/validatornode/application/sender_mock.go similarity index 75% rename from test/node/network/networktest/neighbor_mock.go rename to validatornode/application/sender_mock.go index fab4a7c7..8ad85cbd 100644 --- a/test/node/network/networktest/neighbor_mock.go +++ b/validatornode/application/sender_mock.go @@ -1,23 +1,22 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package networktest +package application import ( - "github.com/my-cloud/ruthenium/src/node/network" "sync" ) -// Ensure, that NeighborMock does implement Neighbor. +// Ensure, that SenderMock does implement Sender. // If this is not the case, regenerate this file with moq. -var _ network.Neighbor = &NeighborMock{} +var _ Sender = &SenderMock{} -// NeighborMock is a mock implementation of Neighbor. +// SenderMock is a mock implementation of Sender. // -// func TestSomethingThatUsesNeighbor(t *testing.T) { +// func TestSomethingThatUsesSender(t *testing.T) { // -// // make and configure a mocked Neighbor -// mockedNeighbor := &NeighborMock{ +// // make and configure a mocked Sender +// mockedSender := &SenderMock{ // AddTransactionFunc: func(transaction []byte) error { // panic("mock out the AddTransaction method") // }, @@ -44,11 +43,11 @@ var _ network.Neighbor = &NeighborMock{} // }, // } // -// // use mockedNeighbor in code that requires Neighbor +// // use mockedSender in code that requires Sender // // and then make assertions. // // } -type NeighborMock struct { +type SenderMock struct { // AddTransactionFunc mocks the AddTransaction method. AddTransactionFunc func(transaction []byte) error @@ -119,9 +118,9 @@ type NeighborMock struct { } // AddTransaction calls AddTransactionFunc. -func (mock *NeighborMock) AddTransaction(transaction []byte) error { +func (mock *SenderMock) AddTransaction(transaction []byte) error { if mock.AddTransactionFunc == nil { - panic("NeighborMock.AddTransactionFunc: method is nil but Neighbor.AddTransaction was just called") + panic("SenderMock.AddTransactionFunc: method is nil but Sender.AddTransaction was just called") } callInfo := struct { Transaction []byte @@ -137,8 +136,8 @@ func (mock *NeighborMock) AddTransaction(transaction []byte) error { // AddTransactionCalls gets all the calls that were made to AddTransaction. // Check the length with: // -// len(mockedNeighbor.AddTransactionCalls()) -func (mock *NeighborMock) AddTransactionCalls() []struct { +// len(mockedSender.AddTransactionCalls()) +func (mock *SenderMock) AddTransactionCalls() []struct { Transaction []byte } { var calls []struct { @@ -151,9 +150,9 @@ func (mock *NeighborMock) AddTransactionCalls() []struct { } // GetBlocks calls GetBlocksFunc. -func (mock *NeighborMock) GetBlocks(startingBlockHeight uint64) ([]byte, error) { +func (mock *SenderMock) GetBlocks(startingBlockHeight uint64) ([]byte, error) { if mock.GetBlocksFunc == nil { - panic("NeighborMock.GetBlocksFunc: method is nil but Neighbor.GetBlocks was just called") + panic("SenderMock.GetBlocksFunc: method is nil but Sender.GetBlocks was just called") } callInfo := struct { StartingBlockHeight uint64 @@ -169,8 +168,8 @@ func (mock *NeighborMock) GetBlocks(startingBlockHeight uint64) ([]byte, error) // GetBlocksCalls gets all the calls that were made to GetBlocks. // Check the length with: // -// len(mockedNeighbor.GetBlocksCalls()) -func (mock *NeighborMock) GetBlocksCalls() []struct { +// len(mockedSender.GetBlocksCalls()) +func (mock *SenderMock) GetBlocksCalls() []struct { StartingBlockHeight uint64 } { var calls []struct { @@ -183,9 +182,9 @@ func (mock *NeighborMock) GetBlocksCalls() []struct { } // GetFirstBlockTimestamp calls GetFirstBlockTimestampFunc. -func (mock *NeighborMock) GetFirstBlockTimestamp() (int64, error) { +func (mock *SenderMock) GetFirstBlockTimestamp() (int64, error) { if mock.GetFirstBlockTimestampFunc == nil { - panic("NeighborMock.GetFirstBlockTimestampFunc: method is nil but Neighbor.GetFirstBlockTimestamp was just called") + panic("SenderMock.GetFirstBlockTimestampFunc: method is nil but Sender.GetFirstBlockTimestamp was just called") } callInfo := struct { }{} @@ -198,8 +197,8 @@ func (mock *NeighborMock) GetFirstBlockTimestamp() (int64, error) { // GetFirstBlockTimestampCalls gets all the calls that were made to GetFirstBlockTimestamp. // Check the length with: // -// len(mockedNeighbor.GetFirstBlockTimestampCalls()) -func (mock *NeighborMock) GetFirstBlockTimestampCalls() []struct { +// len(mockedSender.GetFirstBlockTimestampCalls()) +func (mock *SenderMock) GetFirstBlockTimestampCalls() []struct { } { var calls []struct { } @@ -210,9 +209,9 @@ func (mock *NeighborMock) GetFirstBlockTimestampCalls() []struct { } // GetSettings calls GetSettingsFunc. -func (mock *NeighborMock) GetSettings() ([]byte, error) { +func (mock *SenderMock) GetSettings() ([]byte, error) { if mock.GetSettingsFunc == nil { - panic("NeighborMock.GetSettingsFunc: method is nil but Neighbor.GetSettings was just called") + panic("SenderMock.GetSettingsFunc: method is nil but Sender.GetSettings was just called") } callInfo := struct { }{} @@ -225,8 +224,8 @@ func (mock *NeighborMock) GetSettings() ([]byte, error) { // GetSettingsCalls gets all the calls that were made to GetSettings. // Check the length with: // -// len(mockedNeighbor.GetSettingsCalls()) -func (mock *NeighborMock) GetSettingsCalls() []struct { +// len(mockedSender.GetSettingsCalls()) +func (mock *SenderMock) GetSettingsCalls() []struct { } { var calls []struct { } @@ -237,9 +236,9 @@ func (mock *NeighborMock) GetSettingsCalls() []struct { } // GetTransactions calls GetTransactionsFunc. -func (mock *NeighborMock) GetTransactions() ([]byte, error) { +func (mock *SenderMock) GetTransactions() ([]byte, error) { if mock.GetTransactionsFunc == nil { - panic("NeighborMock.GetTransactionsFunc: method is nil but Neighbor.GetTransactions was just called") + panic("SenderMock.GetTransactionsFunc: method is nil but Sender.GetTransactions was just called") } callInfo := struct { }{} @@ -252,8 +251,8 @@ func (mock *NeighborMock) GetTransactions() ([]byte, error) { // GetTransactionsCalls gets all the calls that were made to GetTransactions. // Check the length with: // -// len(mockedNeighbor.GetTransactionsCalls()) -func (mock *NeighborMock) GetTransactionsCalls() []struct { +// len(mockedSender.GetTransactionsCalls()) +func (mock *SenderMock) GetTransactionsCalls() []struct { } { var calls []struct { } @@ -264,9 +263,9 @@ func (mock *NeighborMock) GetTransactionsCalls() []struct { } // GetUtxos calls GetUtxosFunc. -func (mock *NeighborMock) GetUtxos(address string) ([]byte, error) { +func (mock *SenderMock) GetUtxos(address string) ([]byte, error) { if mock.GetUtxosFunc == nil { - panic("NeighborMock.GetUtxosFunc: method is nil but Neighbor.GetUtxos was just called") + panic("SenderMock.GetUtxosFunc: method is nil but Sender.GetUtxos was just called") } callInfo := struct { Address string @@ -282,8 +281,8 @@ func (mock *NeighborMock) GetUtxos(address string) ([]byte, error) { // GetUtxosCalls gets all the calls that were made to GetUtxos. // Check the length with: // -// len(mockedNeighbor.GetUtxosCalls()) -func (mock *NeighborMock) GetUtxosCalls() []struct { +// len(mockedSender.GetUtxosCalls()) +func (mock *SenderMock) GetUtxosCalls() []struct { Address string } { var calls []struct { @@ -296,9 +295,9 @@ func (mock *NeighborMock) GetUtxosCalls() []struct { } // SendTargets calls SendTargetsFunc. -func (mock *NeighborMock) SendTargets(targets []string) error { +func (mock *SenderMock) SendTargets(targets []string) error { if mock.SendTargetsFunc == nil { - panic("NeighborMock.SendTargetsFunc: method is nil but Neighbor.SendTargets was just called") + panic("SenderMock.SendTargetsFunc: method is nil but Sender.SendTargets was just called") } callInfo := struct { Targets []string @@ -314,8 +313,8 @@ func (mock *NeighborMock) SendTargets(targets []string) error { // SendTargetsCalls gets all the calls that were made to SendTargets. // Check the length with: // -// len(mockedNeighbor.SendTargetsCalls()) -func (mock *NeighborMock) SendTargetsCalls() []struct { +// len(mockedSender.SendTargetsCalls()) +func (mock *SenderMock) SendTargetsCalls() []struct { Targets []string } { var calls []struct { @@ -328,9 +327,9 @@ func (mock *NeighborMock) SendTargetsCalls() []struct { } // Target calls TargetFunc. -func (mock *NeighborMock) Target() string { +func (mock *SenderMock) Target() string { if mock.TargetFunc == nil { - panic("NeighborMock.TargetFunc: method is nil but Neighbor.Target was just called") + panic("SenderMock.TargetFunc: method is nil but Sender.Target was just called") } callInfo := struct { }{} @@ -343,8 +342,8 @@ func (mock *NeighborMock) Target() string { // TargetCalls gets all the calls that were made to Target. // Check the length with: // -// len(mockedNeighbor.TargetCalls()) -func (mock *NeighborMock) TargetCalls() []struct { +// len(mockedSender.TargetCalls()) +func (mock *SenderMock) TargetCalls() []struct { } { var calls []struct { } diff --git a/src/node/network/synchronizer.go b/validatornode/application/senders_manager.go similarity index 52% rename from src/node/network/synchronizer.go rename to validatornode/application/senders_manager.go index e01bee54..59451edd 100644 --- a/src/node/network/synchronizer.go +++ b/validatornode/application/senders_manager.go @@ -1,8 +1,8 @@ -package network +package application -type Synchronizer interface { +type SendersManager interface { AddTargets(targets []string) HostTarget() string Incentive(target string) - Neighbors() []Neighbor + Senders() []Sender } diff --git a/test/node/network/networktest/synchronizer_mock.go b/validatornode/application/senders_manager_mock.go similarity index 57% rename from test/node/network/networktest/synchronizer_mock.go rename to validatornode/application/senders_manager_mock.go index 39114673..6c068dd3 100644 --- a/test/node/network/networktest/synchronizer_mock.go +++ b/validatornode/application/senders_manager_mock.go @@ -1,23 +1,22 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package networktest +package application import ( - "github.com/my-cloud/ruthenium/src/node/network" "sync" ) -// Ensure, that SynchronizerMock does implement Synchronizer. +// Ensure, that SendersManagerMock does implement SendersManager. // If this is not the case, regenerate this file with moq. -var _ network.Synchronizer = &SynchronizerMock{} +var _ SendersManager = &SendersManagerMock{} -// SynchronizerMock is a mock implementation of Synchronizer. +// SendersManagerMock is a mock implementation of SendersManager. // -// func TestSomethingThatUsesSynchronizer(t *testing.T) { +// func TestSomethingThatUsesSendersManager(t *testing.T) { // -// // make and configure a mocked Synchronizer -// mockedSynchronizer := &SynchronizerMock{ +// // make and configure a mocked SendersManager +// mockedSendersManager := &SendersManagerMock{ // AddTargetsFunc: func(targets []string) { // panic("mock out the AddTargets method") // }, @@ -27,16 +26,16 @@ var _ network.Synchronizer = &SynchronizerMock{} // IncentiveFunc: func(target string) { // panic("mock out the Incentive method") // }, -// NeighborsFunc: func() []Neighbor { -// panic("mock out the Neighbors method") +// SendersFunc: func() []Sender { +// panic("mock out the Senders method") // }, // } // -// // use mockedSynchronizer in code that requires Synchronizer +// // use mockedSendersManager in code that requires SendersManager // // and then make assertions. // // } -type SynchronizerMock struct { +type SendersManagerMock struct { // AddTargetsFunc mocks the AddTargets method. AddTargetsFunc func(targets []string) @@ -46,8 +45,8 @@ type SynchronizerMock struct { // IncentiveFunc mocks the Incentive method. IncentiveFunc func(target string) - // NeighborsFunc mocks the Neighbors method. - NeighborsFunc func() []network.Neighbor + // SendersFunc mocks the Senders method. + SendersFunc func() []Sender // calls tracks calls to the methods. calls struct { @@ -64,20 +63,20 @@ type SynchronizerMock struct { // Target is the target argument value. Target string } - // Neighbors holds details about calls to the Neighbors method. - Neighbors []struct { + // Senders holds details about calls to the Senders method. + Senders []struct { } } lockAddTargets sync.RWMutex lockHostTarget sync.RWMutex lockIncentive sync.RWMutex - lockNeighbors sync.RWMutex + lockSenders sync.RWMutex } // AddTargets calls AddTargetsFunc. -func (mock *SynchronizerMock) AddTargets(targets []string) { +func (mock *SendersManagerMock) AddTargets(targets []string) { if mock.AddTargetsFunc == nil { - panic("SynchronizerMock.AddTargetsFunc: method is nil but Synchronizer.AddTargets was just called") + panic("SendersManagerMock.AddTargetsFunc: method is nil but SendersManager.AddTargets was just called") } callInfo := struct { Targets []string @@ -93,8 +92,8 @@ func (mock *SynchronizerMock) AddTargets(targets []string) { // AddTargetsCalls gets all the calls that were made to AddTargets. // Check the length with: // -// len(mockedSynchronizer.AddTargetsCalls()) -func (mock *SynchronizerMock) AddTargetsCalls() []struct { +// len(mockedSendersManager.AddTargetsCalls()) +func (mock *SendersManagerMock) AddTargetsCalls() []struct { Targets []string } { var calls []struct { @@ -107,9 +106,9 @@ func (mock *SynchronizerMock) AddTargetsCalls() []struct { } // HostTarget calls HostTargetFunc. -func (mock *SynchronizerMock) HostTarget() string { +func (mock *SendersManagerMock) HostTarget() string { if mock.HostTargetFunc == nil { - panic("SynchronizerMock.HostTargetFunc: method is nil but Synchronizer.HostTarget was just called") + panic("SendersManagerMock.HostTargetFunc: method is nil but SendersManager.HostTarget was just called") } callInfo := struct { }{} @@ -122,8 +121,8 @@ func (mock *SynchronizerMock) HostTarget() string { // HostTargetCalls gets all the calls that were made to HostTarget. // Check the length with: // -// len(mockedSynchronizer.HostTargetCalls()) -func (mock *SynchronizerMock) HostTargetCalls() []struct { +// len(mockedSendersManager.HostTargetCalls()) +func (mock *SendersManagerMock) HostTargetCalls() []struct { } { var calls []struct { } @@ -134,9 +133,9 @@ func (mock *SynchronizerMock) HostTargetCalls() []struct { } // Incentive calls IncentiveFunc. -func (mock *SynchronizerMock) Incentive(target string) { +func (mock *SendersManagerMock) Incentive(target string) { if mock.IncentiveFunc == nil { - panic("SynchronizerMock.IncentiveFunc: method is nil but Synchronizer.Incentive was just called") + panic("SendersManagerMock.IncentiveFunc: method is nil but SendersManager.Incentive was just called") } callInfo := struct { Target string @@ -152,8 +151,8 @@ func (mock *SynchronizerMock) Incentive(target string) { // IncentiveCalls gets all the calls that were made to Incentive. // Check the length with: // -// len(mockedSynchronizer.IncentiveCalls()) -func (mock *SynchronizerMock) IncentiveCalls() []struct { +// len(mockedSendersManager.IncentiveCalls()) +func (mock *SendersManagerMock) IncentiveCalls() []struct { Target string } { var calls []struct { @@ -165,29 +164,29 @@ func (mock *SynchronizerMock) IncentiveCalls() []struct { return calls } -// Neighbors calls NeighborsFunc. -func (mock *SynchronizerMock) Neighbors() []network.Neighbor { - if mock.NeighborsFunc == nil { - panic("SynchronizerMock.NeighborsFunc: method is nil but Synchronizer.Neighbors was just called") +// Senders calls SendersFunc. +func (mock *SendersManagerMock) Senders() []Sender { + if mock.SendersFunc == nil { + panic("SendersManagerMock.SendersFunc: method is nil but SendersManager.Senders was just called") } callInfo := struct { }{} - mock.lockNeighbors.Lock() - mock.calls.Neighbors = append(mock.calls.Neighbors, callInfo) - mock.lockNeighbors.Unlock() - return mock.NeighborsFunc() + mock.lockSenders.Lock() + mock.calls.Senders = append(mock.calls.Senders, callInfo) + mock.lockSenders.Unlock() + return mock.SendersFunc() } -// NeighborsCalls gets all the calls that were made to Neighbors. +// SendersCalls gets all the calls that were made to Senders. // Check the length with: // -// len(mockedSynchronizer.NeighborsCalls()) -func (mock *SynchronizerMock) NeighborsCalls() []struct { +// len(mockedSendersManager.SendersCalls()) +func (mock *SendersManagerMock) SendersCalls() []struct { } { var calls []struct { } - mock.lockNeighbors.RLock() - calls = mock.calls.Neighbors - mock.lockNeighbors.RUnlock() + mock.lockSenders.RLock() + calls = mock.calls.Senders + mock.lockSenders.RUnlock() return calls } diff --git a/validatornode/application/time_provider.go b/validatornode/application/time_provider.go new file mode 100644 index 00000000..42e1ceae --- /dev/null +++ b/validatornode/application/time_provider.go @@ -0,0 +1,7 @@ +package application + +import "time" + +type TimeProvider interface { + Now() time.Time +} diff --git a/validatornode/application/time_provider_mock.go b/validatornode/application/time_provider_mock.go new file mode 100644 index 00000000..0e4c65a5 --- /dev/null +++ b/validatornode/application/time_provider_mock.go @@ -0,0 +1,68 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "sync" + "time" +) + +// Ensure, that TimeProviderMock does implement TimeProvider. +// If this is not the case, regenerate this file with moq. +var _ TimeProvider = &TimeProviderMock{} + +// TimeProviderMock is a mock implementation of TimeProvider. +// +// func TestSomethingThatUsesTimeProvider(t *testing.T) { +// +// // make and configure a mocked TimeProvider +// mockedTimeProvider := &TimeProviderMock{ +// NowFunc: func() time.Time { +// panic("mock out the Now method") +// }, +// } +// +// // use mockedTimeProvider in code that requires TimeProvider +// // and then make assertions. +// +// } +type TimeProviderMock struct { + // NowFunc mocks the Now method. + NowFunc func() time.Time + + // calls tracks calls to the methods. + calls struct { + // Now holds details about calls to the Now method. + Now []struct { + } + } + lockNow sync.RWMutex +} + +// Now calls NowFunc. +func (mock *TimeProviderMock) Now() time.Time { + if mock.NowFunc == nil { + panic("TimeProviderMock.NowFunc: method is nil but TimeProvider.Now was just called") + } + callInfo := struct { + }{} + mock.lockNow.Lock() + mock.calls.Now = append(mock.calls.Now, callInfo) + mock.lockNow.Unlock() + return mock.NowFunc() +} + +// NowCalls gets all the calls that were made to Now. +// Check the length with: +// +// len(mockedTimeProvider.NowCalls()) +func (mock *TimeProviderMock) NowCalls() []struct { +} { + var calls []struct { + } + mock.lockNow.RLock() + calls = mock.calls.Now + mock.lockNow.RUnlock() + return calls +} diff --git a/validatornode/application/transactions_manager.go b/validatornode/application/transactions_manager.go new file mode 100644 index 00000000..6f6d9b55 --- /dev/null +++ b/validatornode/application/transactions_manager.go @@ -0,0 +1,8 @@ +package application + +import "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + +type TransactionsManager interface { + AddTransaction(transaction *ledger.Transaction, broadcasterTarget string, hostTarget string) + Transactions() []*ledger.Transaction +} diff --git a/validatornode/application/transactions_manager_mock.go b/validatornode/application/transactions_manager_mock.go new file mode 100644 index 00000000..635eb9ac --- /dev/null +++ b/validatornode/application/transactions_manager_mock.go @@ -0,0 +1,124 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "sync" +) + +// Ensure, that TransactionsManagerMock does implement TransactionsManager. +// If this is not the case, regenerate this file with moq. +var _ TransactionsManager = &TransactionsManagerMock{} + +// TransactionsManagerMock is a mock implementation of TransactionsManager. +// +// func TestSomethingThatUsesTransactionsManager(t *testing.T) { +// +// // make and configure a mocked TransactionsManager +// mockedTransactionsManager := &TransactionsManagerMock{ +// AddTransactionFunc: func(transaction *ledger.Transaction, broadcasterTarget string, hostTarget string) { +// panic("mock out the AddTransaction method") +// }, +// TransactionsFunc: func() []*ledger.Transaction { +// panic("mock out the Transactions method") +// }, +// } +// +// // use mockedTransactionsManager in code that requires TransactionsManager +// // and then make assertions. +// +// } +type TransactionsManagerMock struct { + // AddTransactionFunc mocks the AddTransaction method. + AddTransactionFunc func(transaction *ledger.Transaction, broadcasterTarget string, hostTarget string) + + // TransactionsFunc mocks the Transactions method. + TransactionsFunc func() []*ledger.Transaction + + // calls tracks calls to the methods. + calls struct { + // AddTransaction holds details about calls to the AddTransaction method. + AddTransaction []struct { + // Transaction is the transaction argument value. + Transaction *ledger.Transaction + // BroadcasterTarget is the broadcasterTarget argument value. + BroadcasterTarget string + // HostTarget is the hostTarget argument value. + HostTarget string + } + // Transactions holds details about calls to the Transactions method. + Transactions []struct { + } + } + lockAddTransaction sync.RWMutex + lockTransactions sync.RWMutex +} + +// AddTransaction calls AddTransactionFunc. +func (mock *TransactionsManagerMock) AddTransaction(transaction *ledger.Transaction, broadcasterTarget string, hostTarget string) { + if mock.AddTransactionFunc == nil { + panic("TransactionsManagerMock.AddTransactionFunc: method is nil but TransactionsManager.AddTransaction was just called") + } + callInfo := struct { + Transaction *ledger.Transaction + BroadcasterTarget string + HostTarget string + }{ + Transaction: transaction, + BroadcasterTarget: broadcasterTarget, + HostTarget: hostTarget, + } + mock.lockAddTransaction.Lock() + mock.calls.AddTransaction = append(mock.calls.AddTransaction, callInfo) + mock.lockAddTransaction.Unlock() + mock.AddTransactionFunc(transaction, broadcasterTarget, hostTarget) +} + +// AddTransactionCalls gets all the calls that were made to AddTransaction. +// Check the length with: +// +// len(mockedTransactionsManager.AddTransactionCalls()) +func (mock *TransactionsManagerMock) AddTransactionCalls() []struct { + Transaction *ledger.Transaction + BroadcasterTarget string + HostTarget string +} { + var calls []struct { + Transaction *ledger.Transaction + BroadcasterTarget string + HostTarget string + } + mock.lockAddTransaction.RLock() + calls = mock.calls.AddTransaction + mock.lockAddTransaction.RUnlock() + return calls +} + +// Transactions calls TransactionsFunc. +func (mock *TransactionsManagerMock) Transactions() []*ledger.Transaction { + if mock.TransactionsFunc == nil { + panic("TransactionsManagerMock.TransactionsFunc: method is nil but TransactionsManager.Transactions was just called") + } + callInfo := struct { + }{} + mock.lockTransactions.Lock() + mock.calls.Transactions = append(mock.calls.Transactions, callInfo) + mock.lockTransactions.Unlock() + return mock.TransactionsFunc() +} + +// TransactionsCalls gets all the calls that were made to Transactions. +// Check the length with: +// +// len(mockedTransactionsManager.TransactionsCalls()) +func (mock *TransactionsManagerMock) TransactionsCalls() []struct { +} { + var calls []struct { + } + mock.lockTransactions.RLock() + calls = mock.calls.Transactions + mock.lockTransactions.RUnlock() + return calls +} diff --git a/validatornode/application/utxos_manager.go b/validatornode/application/utxos_manager.go new file mode 100644 index 00000000..7f77a397 --- /dev/null +++ b/validatornode/application/utxos_manager.go @@ -0,0 +1,11 @@ +package application + +import "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + +type UtxosManager interface { + CalculateFee(transaction *ledger.Transaction, timestamp int64) (uint64, error) + Clear() + Copy() UtxosManager + UpdateUtxos(transactions []*ledger.Transaction, timestamp int64) error + Utxos(address string) []*ledger.Utxo +} diff --git a/validatornode/application/utxos_manager_mock.go b/validatornode/application/utxos_manager_mock.go new file mode 100644 index 00000000..8b7a71ad --- /dev/null +++ b/validatornode/application/utxos_manager_mock.go @@ -0,0 +1,249 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package application + +import ( + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "sync" +) + +// Ensure, that UtxosManagerMock does implement UtxosManager. +// If this is not the case, regenerate this file with moq. +var _ UtxosManager = &UtxosManagerMock{} + +// UtxosManagerMock is a mock implementation of UtxosManager. +// +// func TestSomethingThatUsesUtxosManager(t *testing.T) { +// +// // make and configure a mocked UtxosManager +// mockedUtxosManager := &UtxosManagerMock{ +// CalculateFeeFunc: func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { +// panic("mock out the CalculateFee method") +// }, +// ClearFunc: func() { +// panic("mock out the Clear method") +// }, +// CopyFunc: func() UtxosManager { +// panic("mock out the Copy method") +// }, +// UpdateUtxosFunc: func(transactions []*ledger.Transaction, timestamp int64) error { +// panic("mock out the UpdateUtxos method") +// }, +// UtxosFunc: func(address string) []*ledger.Utxo { +// panic("mock out the Utxos method") +// }, +// } +// +// // use mockedUtxosManager in code that requires UtxosManager +// // and then make assertions. +// +// } +type UtxosManagerMock struct { + // CalculateFeeFunc mocks the CalculateFee method. + CalculateFeeFunc func(transaction *ledger.Transaction, timestamp int64) (uint64, error) + + // ClearFunc mocks the Clear method. + ClearFunc func() + + // CopyFunc mocks the Copy method. + CopyFunc func() UtxosManager + + // UpdateUtxosFunc mocks the UpdateUtxos method. + UpdateUtxosFunc func(transactions []*ledger.Transaction, timestamp int64) error + + // UtxosFunc mocks the Utxos method. + UtxosFunc func(address string) []*ledger.Utxo + + // calls tracks calls to the methods. + calls struct { + // CalculateFee holds details about calls to the CalculateFee method. + CalculateFee []struct { + // Transaction is the transaction argument value. + Transaction *ledger.Transaction + // Timestamp is the timestamp argument value. + Timestamp int64 + } + // Clear holds details about calls to the Clear method. + Clear []struct { + } + // Copy holds details about calls to the Copy method. + Copy []struct { + } + // UpdateUtxos holds details about calls to the UpdateUtxos method. + UpdateUtxos []struct { + // Transactions is the transactions argument value. + Transactions []*ledger.Transaction + // Timestamp is the timestamp argument value. + Timestamp int64 + } + // Utxos holds details about calls to the Utxos method. + Utxos []struct { + // Address is the address argument value. + Address string + } + } + lockCalculateFee sync.RWMutex + lockClear sync.RWMutex + lockCopy sync.RWMutex + lockUpdateUtxos sync.RWMutex + lockUtxos sync.RWMutex +} + +// CalculateFee calls CalculateFeeFunc. +func (mock *UtxosManagerMock) CalculateFee(transaction *ledger.Transaction, timestamp int64) (uint64, error) { + if mock.CalculateFeeFunc == nil { + panic("UtxosManagerMock.CalculateFeeFunc: method is nil but UtxosManager.CalculateFee was just called") + } + callInfo := struct { + Transaction *ledger.Transaction + Timestamp int64 + }{ + Transaction: transaction, + Timestamp: timestamp, + } + mock.lockCalculateFee.Lock() + mock.calls.CalculateFee = append(mock.calls.CalculateFee, callInfo) + mock.lockCalculateFee.Unlock() + return mock.CalculateFeeFunc(transaction, timestamp) +} + +// CalculateFeeCalls gets all the calls that were made to CalculateFee. +// Check the length with: +// +// len(mockedUtxosManager.CalculateFeeCalls()) +func (mock *UtxosManagerMock) CalculateFeeCalls() []struct { + Transaction *ledger.Transaction + Timestamp int64 +} { + var calls []struct { + Transaction *ledger.Transaction + Timestamp int64 + } + mock.lockCalculateFee.RLock() + calls = mock.calls.CalculateFee + mock.lockCalculateFee.RUnlock() + return calls +} + +// Clear calls ClearFunc. +func (mock *UtxosManagerMock) Clear() { + if mock.ClearFunc == nil { + panic("UtxosManagerMock.ClearFunc: method is nil but UtxosManager.Clear was just called") + } + callInfo := struct { + }{} + mock.lockClear.Lock() + mock.calls.Clear = append(mock.calls.Clear, callInfo) + mock.lockClear.Unlock() + mock.ClearFunc() +} + +// ClearCalls gets all the calls that were made to Clear. +// Check the length with: +// +// len(mockedUtxosManager.ClearCalls()) +func (mock *UtxosManagerMock) ClearCalls() []struct { +} { + var calls []struct { + } + mock.lockClear.RLock() + calls = mock.calls.Clear + mock.lockClear.RUnlock() + return calls +} + +// Copy calls CopyFunc. +func (mock *UtxosManagerMock) Copy() UtxosManager { + if mock.CopyFunc == nil { + panic("UtxosManagerMock.CopyFunc: method is nil but UtxosManager.Copy was just called") + } + callInfo := struct { + }{} + mock.lockCopy.Lock() + mock.calls.Copy = append(mock.calls.Copy, callInfo) + mock.lockCopy.Unlock() + return mock.CopyFunc() +} + +// CopyCalls gets all the calls that were made to Copy. +// Check the length with: +// +// len(mockedUtxosManager.CopyCalls()) +func (mock *UtxosManagerMock) CopyCalls() []struct { +} { + var calls []struct { + } + mock.lockCopy.RLock() + calls = mock.calls.Copy + mock.lockCopy.RUnlock() + return calls +} + +// UpdateUtxos calls UpdateUtxosFunc. +func (mock *UtxosManagerMock) UpdateUtxos(transactions []*ledger.Transaction, timestamp int64) error { + if mock.UpdateUtxosFunc == nil { + panic("UtxosManagerMock.UpdateUtxosFunc: method is nil but UtxosManager.UpdateUtxos was just called") + } + callInfo := struct { + Transactions []*ledger.Transaction + Timestamp int64 + }{ + Transactions: transactions, + Timestamp: timestamp, + } + mock.lockUpdateUtxos.Lock() + mock.calls.UpdateUtxos = append(mock.calls.UpdateUtxos, callInfo) + mock.lockUpdateUtxos.Unlock() + return mock.UpdateUtxosFunc(transactions, timestamp) +} + +// UpdateUtxosCalls gets all the calls that were made to UpdateUtxos. +// Check the length with: +// +// len(mockedUtxosManager.UpdateUtxosCalls()) +func (mock *UtxosManagerMock) UpdateUtxosCalls() []struct { + Transactions []*ledger.Transaction + Timestamp int64 +} { + var calls []struct { + Transactions []*ledger.Transaction + Timestamp int64 + } + mock.lockUpdateUtxos.RLock() + calls = mock.calls.UpdateUtxos + mock.lockUpdateUtxos.RUnlock() + return calls +} + +// Utxos calls UtxosFunc. +func (mock *UtxosManagerMock) Utxos(address string) []*ledger.Utxo { + if mock.UtxosFunc == nil { + panic("UtxosManagerMock.UtxosFunc: method is nil but UtxosManager.Utxos was just called") + } + callInfo := struct { + Address string + }{ + Address: address, + } + mock.lockUtxos.Lock() + mock.calls.Utxos = append(mock.calls.Utxos, callInfo) + mock.lockUtxos.Unlock() + return mock.UtxosFunc(address) +} + +// UtxosCalls gets all the calls that were made to Utxos. +// Check the length with: +// +// len(mockedUtxosManager.UtxosCalls()) +func (mock *UtxosManagerMock) UtxosCalls() []struct { + Address string +} { + var calls []struct { + Address string + } + mock.lockUtxos.RLock() + calls = mock.calls.Utxos + mock.lockUtxos.RUnlock() + return calls +} diff --git a/validatornode/application/validation/transactions_pool.go b/validatornode/application/validation/transactions_pool.go new file mode 100644 index 00000000..975ee591 --- /dev/null +++ b/validatornode/application/validation/transactions_pool.go @@ -0,0 +1,202 @@ +package validation + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "math/rand" + "sync" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type TransactionsPool struct { + transactions []*ledger.Transaction + mutex sync.RWMutex + + blocksManager application.BlocksManager + settings application.ProtocolSettingsProvider + sendersManager application.SendersManager + utxosManager application.UtxosManager + validatorAddress string + + logger log.Logger +} + +func NewTransactionsPool(blocksManager application.BlocksManager, settings application.ProtocolSettingsProvider, sendersManager application.SendersManager, utxosManager application.UtxosManager, validatorAddress string, logger log.Logger) *TransactionsPool { + pool := new(TransactionsPool) + pool.blocksManager = blocksManager + pool.settings = settings + pool.sendersManager = sendersManager + pool.utxosManager = utxosManager + pool.validatorAddress = validatorAddress + pool.logger = logger + return pool +} + +func (pool *TransactionsPool) AddTransaction(transaction *ledger.Transaction, broadcasterTarget string, hostTarget string) { + err := pool.addTransaction(transaction) + if err != nil { + pool.logger.Debug(fmt.Errorf("failed to add transaction: %w", err).Error()) + return + } + pool.sendersManager.Incentive(broadcasterTarget) + newTransactionRequest := ledger.NewTransactionRequest(transaction, hostTarget) + marshaledTransactionRequest, err := json.Marshal(newTransactionRequest) + if err != nil { + pool.logger.Debug(fmt.Errorf("failed to marshal transaction request: %w", err).Error()) + return + } + senders := pool.sendersManager.Senders() + for _, sender := range senders { + go func(sender application.Sender) { + _ = sender.AddTransaction(marshaledTransactionRequest) + }(sender) + } +} + +func (pool *TransactionsPool) Transactions() []*ledger.Transaction { + return pool.transactions +} + +func (pool *TransactionsPool) Validate(timestamp int64) { + lastBlockTimestamp := pool.blocksManager.LastBlockTimestamp() + nextBlockTimestamp := lastBlockTimestamp + pool.settings.ValidationTimestamp() + var reward uint64 + var newAddresses []string + var isYielding bool + if lastBlockTimestamp == 0 { + reward = pool.settings.GenesisAmount() + newAddresses = []string{pool.validatorAddress} + isYielding = true + } else if lastBlockTimestamp == timestamp { + pool.logger.Error("unable to create block, a block with the same timestamp is already in the blockchain") + return + } else if timestamp > nextBlockTimestamp { + pool.logger.Error("unable to create block, a block is missing in the blockchain") + return + } + lastBlockTransactions := pool.blocksManager.LastBlockTransactions() + utxosManagerCopy := pool.utxosManager.Copy() + if err := utxosManagerCopy.UpdateUtxos(lastBlockTransactions, nextBlockTimestamp); err != nil { + pool.logger.Error(fmt.Errorf("failed to update UTXOs: %w", err).Error()) + return + } + pool.mutex.Lock() + defer pool.mutex.Unlock() + transactions := pool.transactions + rand.Seed(timestamp) + rand.Shuffle(len(transactions), func(i, j int) { + transactions[i], transactions[j] = transactions[j], transactions[i] + }) + var rejectedTransactions []*ledger.Transaction + for _, transaction := range transactions { + if timestamp < transaction.Timestamp() { + pool.logger.Warn(fmt.Sprintf("transaction removed from the transactions pool, the transaction timestamp is too far in the future, transaction: %v", transaction)) + rejectedTransactions = append(rejectedTransactions, transaction) + continue + } + if transaction.Timestamp() < lastBlockTimestamp { + pool.logger.Warn(fmt.Sprintf("transaction removed from the transactions pool, the transaction timestamp is too old, transaction: %v", transaction)) + rejectedTransactions = append(rejectedTransactions, transaction) + continue + } + if err := transaction.VerifySignatures(); err != nil { + pool.logger.Warn(fmt.Errorf("transaction removed from the transactions pool, failed to verify signature, transaction: %v\n %w", transaction, err).Error()) + rejectedTransactions = append(rejectedTransactions, transaction) + continue + } + fee, err := utxosManagerCopy.CalculateFee(transaction, timestamp) + if err != nil { + pool.logger.Warn(fmt.Errorf("transaction removed from the transactions pool, failed to calculate fee, transaction: %v\n %w", transaction, err).Error()) + rejectedTransactions = append(rejectedTransactions, transaction) + continue + } + if err = utxosManagerCopy.UpdateUtxos([]*ledger.Transaction{transaction}, nextBlockTimestamp); err != nil { + pool.logger.Warn(fmt.Errorf("transaction removed from the transactions pool, failed to update UTXOs, transaction: %v\n %w", transaction, err).Error()) + rejectedTransactions = append(rejectedTransactions, transaction) + continue + } + reward += fee + } + for _, transaction := range rejectedTransactions { + transactions = removeTransaction(transactions, transaction) + } + for _, transaction := range transactions { + for _, output := range transaction.Outputs() { + if output.IsYielding() { + newAddresses = append(newAddresses, output.Address()) + } + } + } + rewardTransaction, err := ledger.NewRewardTransaction(pool.validatorAddress, isYielding, timestamp, reward) + if err != nil { + pool.logger.Error(fmt.Errorf("unable to create block, failed to create reward transaction: %w", err).Error()) + return + } + transactions = append(transactions, rewardTransaction) + err = pool.blocksManager.AddBlock(timestamp, transactions, newAddresses) + if err != nil { + pool.logger.Error(fmt.Errorf("unable to create block: %w", err).Error()) + return + } + pool.clear() + pool.logger.Debug(fmt.Sprintf("reward: %d", reward)) +} + +func (pool *TransactionsPool) addTransaction(transaction *ledger.Transaction) error { + lastBlockTimestamp := pool.blocksManager.LastBlockTimestamp() + if lastBlockTimestamp == 0 { + return errors.New("the blockchain is empty") + } + nextBlockTimestamp := lastBlockTimestamp + pool.settings.ValidationTimestamp() + timestamp := transaction.Timestamp() + if nextBlockTimestamp < timestamp { + return fmt.Errorf("the transaction timestamp is too far in the future: %v, now: %v", time.Unix(0, timestamp), time.Unix(0, nextBlockTimestamp)) + } + currentBlockTimestamp := lastBlockTimestamp + if timestamp < currentBlockTimestamp { + return fmt.Errorf("the transaction timestamp is too old: %v, current block timestamp: %v", time.Unix(0, timestamp), time.Unix(0, currentBlockTimestamp)) + } + for _, pendingTransaction := range pool.transactions { + if transaction.Equals(pendingTransaction) { + return errors.New("the transaction is already in the transactions pool") + } + } + if err := transaction.VerifySignatures(); err != nil { + return fmt.Errorf("failed to verify signature: %w", err) + } + utxoManagerCopy := pool.utxosManager.Copy() + lastBlockTransactions := pool.blocksManager.LastBlockTransactions() + if err := utxoManagerCopy.UpdateUtxos(lastBlockTransactions, nextBlockTimestamp); err != nil { + return fmt.Errorf("failed to update UTXOs: %w", err) + } + if err := utxoManagerCopy.UpdateUtxos(pool.transactions, nextBlockTimestamp); err != nil { + return fmt.Errorf("failed to update UTXOs: %w", err) + } + _, err := utxoManagerCopy.CalculateFee(transaction, nextBlockTimestamp) + if err != nil { + return fmt.Errorf("failed to verify fee: %w", err) + } + pool.mutex.Lock() + defer pool.mutex.Unlock() + pool.transactions = append(pool.transactions, transaction) + return nil +} + +func (pool *TransactionsPool) clear() { + pool.transactions = nil +} + +func removeTransaction(transactions []*ledger.Transaction, removedTransaction *ledger.Transaction) []*ledger.Transaction { + for i := 0; i < len(transactions); i++ { + if transactions[i] == removedTransaction { + transactions = append(transactions[:i], transactions[i+1:]...) + return transactions + } + } + return transactions +} diff --git a/validatornode/application/validation/transactions_pool_test.go b/validatornode/application/validation/transactions_pool_test.go new file mode 100644 index 00000000..c1c3e7e2 --- /dev/null +++ b/validatornode/application/validation/transactions_pool_test.go @@ -0,0 +1,302 @@ +package validation + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_AddTransaction_TransactionTimestampIsInTheFuture_TransactionNotAdded(t *testing.T) { + // Arrange + validatorWalletAddress := test.Address + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + watchMock := new(application.TimeProviderMock) + var now int64 = 2 + watchMock.NowFunc = func() time.Time { return time.Unix(0, now) } + logger := log.NewLoggerMock() + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, validatorWalletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now+2, "0", genesisValue, false) + + // Act + pool.AddTransaction(transaction, "0", "0") + + // Assert + expectedTransactionsLength := 0 + actualTransactionsLength := len(pool.Transactions()) + test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), "failed to add transaction: the transaction timestamp is too far in the future") +} + +func Test_AddTransaction_TransactionTimestampIsTooOld_TransactionNotAdded(t *testing.T) { + // Arrange + validatorWalletAddress := test.Address + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 2 + logger := log.NewLoggerMock() + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, validatorWalletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now-2, "0", genesisValue, false) + + // Act + pool.AddTransaction(transaction, "0", "0") + + // Assert + expectedTransactionsLength := 0 + actualTransactionsLength := len(pool.Transactions()) + test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), "failed to add transaction: the transaction timestamp is too old") +} + +func Test_AddTransaction_InvalidSignature_TransactionNotAdded(t *testing.T) { + // Arrange + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 2 + transactionFee := 0 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + walletAddress := publicKey.Address() + var outputIndex uint16 = 0 + transactionId := "" + privateKey2, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey2) + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, walletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, outputIndex, "A", privateKey2, publicKey, now, transactionId, genesisValue, false) + + // Act + pool.AddTransaction(transaction, "0", "0") + + // Assert + expectedTransactionsLength := 0 + actualTransactionsLength := len(pool.Transactions()) + test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), "failed to add transaction: failed to verify signature") +} + +func Test_AddTransaction_ValidTransaction_TransactionAdded(t *testing.T) { + // Arrange + senderMock := new(application.SenderMock) + senderMock.AddTransactionFunc = func([]byte) error { return nil } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return []application.Sender{senderMock} } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 2 + transactionFee := 0 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(*ledger.Transaction, int64) (uint64, error) { return 0, nil } + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + walletAddress := publicKey.Address() + var outputIndex uint16 = 0 + transactionId := "" + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, walletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, outputIndex, walletAddress, privateKey, publicKey, now, transactionId, genesisValue, false) + + // Act + pool.AddTransaction(transaction, "0", "0") + + // Assert + expectedTransactionsLength := 1 + actualTransactionsLength := len(pool.Transactions()) + test.Assert(t, actualTransactionsLength == expectedTransactionsLength, fmt.Sprintf("Wrong transactions count. Expected: %d - Actual: %d", expectedTransactionsLength, actualTransactionsLength)) +} + +func Test_Validate_BlockAlreadyExist_TransactionsNotValidated(t *testing.T) { + // Arrange + validatorWalletAddress := test.Address + sendersManagerMock := new(application.SendersManagerMock) + var now int64 = 2 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, validatorWalletAddress, logger) + + // Act + pool.Validate(now) + + // Assert + test.AssertThatMessageIsLogged(t, logger.ErrorCalls(), "unable to create block, a block with the same timestamp is already in the blockchain") +} + +func Test_Validate_BlockIsMissing_TransactionsNotValidated(t *testing.T) { + // Arrange + validatorWalletAddress := test.Address + sendersManagerMock := new(application.SendersManagerMock) + var now int64 = 3 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 2 } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, validatorWalletAddress, logger) + + // Act + pool.Validate(now) + + // Assert + test.AssertThatMessageIsLogged(t, logger.ErrorCalls(), "unable to create block, a block is missing in the blockchain") +} + +func Test_Validate_TransactionTimestampIsInTheFuture_TransactionsNotValidated(t *testing.T) { + // Arrange + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 2 + transactionFee := 0 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(*ledger.Transaction, int64) (uint64, error) { return 0, nil } + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + walletAddress := publicKey.Address() + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, walletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now+1, "0", genesisValue, false) + pool.AddTransaction(transaction, "0", "0") + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + + // Act + pool.Validate(now) + + // Assert + test.AssertThatMessageIsLogged(t, logger.WarnCalls(), "transaction removed from the transactions pool, the transaction timestamp is too far in the future") +} + +func Test_Validate_TransactionTimestampIsTooOld_TransactionsNotValidated(t *testing.T) { + // Arrange + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 3 + transactionFee := 0 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 2 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(*ledger.Transaction, int64) (uint64, error) { return 0, nil } + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + walletAddress := publicKey.Address() + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, walletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now-2, "0", genesisValue, false) + pool.AddTransaction(transaction, "0", "0") + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + + // Act + pool.Validate(now) + + // Assert + test.AssertThatMessageIsLogged(t, logger.WarnCalls(), "transaction removed from the transactions pool, the transaction timestamp is too old") +} + +func Test_Validate_ValidTransaction_TransactionsValidated(t *testing.T) { + // Arrange + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { return nil } + sendersManagerMock.IncentiveFunc = func(string) {} + var now int64 = 2 + transactionFee := 0 + logger := log.NewLoggerMock() + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.LastBlockTransactionsFunc = func() []*ledger.Transaction { return nil } + blocksManagerMock.LastBlockTimestampFunc = func() int64 { return now - 1 } + blocksManagerMock.AddBlockFunc = func(int64, []*ledger.Transaction, []string) error { return nil } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(*ledger.Transaction, int64) (uint64, error) { return 0, nil } + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + walletAddress := publicKey.Address() + pool := NewTransactionsPool(blocksManagerMock, settings, sendersManagerMock, utxosManagerMock, walletAddress, logger) + var genesisValue uint64 = 0 + transaction := ledger.NewSignedTransaction(genesisValue, transactionFee, 0, "A", privateKey, publicKey, now, "0", genesisValue, false) + pool.AddTransaction(transaction, "0", "0") + + // Act + pool.Validate(now) + + // Assert + addBlockCalls := blocksManagerMock.AddBlockCalls() + test.Assert(t, len(addBlockCalls) == 1, fmt.Sprintf("AddBlock method should be called only once whereas it's called %d times", len(addBlockCalls))) + transactions := addBlockCalls[0].Transactions + isTwoTransactions := len(transactions) == 2 + test.Assert(t, isTwoTransactions, "Validated transactions pool should contain exactly 2 transactions.") + actualTransaction := transactions[0] + test.Assert(t, actualTransaction.Equals(transaction), "The first validated transaction is not the expected one.") + rewardTransaction := transactions[1] + isRewardTransaction := rewardTransaction.HasReward() + test.Assert(t, isRewardTransaction, "The second validated transaction should be the reward.") +} diff --git a/validatornode/application/verification/addresses_registry.go b/validatornode/application/verification/addresses_registry.go new file mode 100644 index 00000000..3664b7b7 --- /dev/null +++ b/validatornode/application/verification/addresses_registry.go @@ -0,0 +1,118 @@ +package verification + +import ( + "github.com/my-cloud/ruthenium/validatornode/application" + "sync" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type AddressesRegistry struct { + humansManager HumansManager + registeredMutex sync.RWMutex + temporaryMutex sync.RWMutex + removedMutex sync.RWMutex + registeredAddresses map[string]bool + removedAddresses []string + logger log.Logger +} + +func NewAddressesRegistry(humansManager HumansManager, logger log.Logger) *AddressesRegistry { + registry := &AddressesRegistry{} + registry.humansManager = humansManager + registry.registeredAddresses = make(map[string]bool) + registry.logger = logger + return registry +} + +func (registry *AddressesRegistry) Clear() { + registry.registeredMutex.Lock() + defer registry.registeredMutex.Unlock() + registry.temporaryMutex.Lock() + defer registry.temporaryMutex.Unlock() + registry.removedMutex.Lock() + defer registry.removedMutex.Unlock() + registry.registeredAddresses = make(map[string]bool) + registry.removedAddresses = nil +} + +func (registry *AddressesRegistry) Copy() application.AddressesManager { + registry.registeredMutex.RLock() + defer registry.registeredMutex.RUnlock() + registry.temporaryMutex.RLock() + defer registry.temporaryMutex.RUnlock() + registry.removedMutex.RLock() + defer registry.removedMutex.RUnlock() + registryCopy := &AddressesRegistry{} + registryCopy.humansManager = registry.humansManager + registryCopy.registeredAddresses = copyAddressesMap(registry.registeredAddresses) + registryCopy.removedAddresses = registry.removedAddresses + registryCopy.logger = registry.logger + return registryCopy +} + +func (registry *AddressesRegistry) Filter(addresses []string) []string { + var newAddresses []string + for _, address := range addresses { + if !registry.registeredAddresses[address] { + newAddresses = append(newAddresses, address) + } + } + return newAddresses +} + +func (registry *AddressesRegistry) IsRegistered(address string) bool { + return registry.registeredAddresses[address] +} + +func (registry *AddressesRegistry) RemovedAddresses() []string { + return registry.removedAddresses +} + +func (registry *AddressesRegistry) Synchronize(_ int64) { + registry.registeredMutex.RLock() + defer registry.registeredMutex.RUnlock() + registry.removedMutex.Lock() + defer registry.removedMutex.Unlock() + for address := range registry.registeredAddresses { + isPohValid, err := registry.humansManager.IsRegistered(address) + if err != nil { + registry.logger.Debug(err.Error()) + } else if !isPohValid { + registry.removedAddresses = append(registry.removedAddresses, address) + } + } + registry.temporaryMutex.Lock() + defer registry.temporaryMutex.Unlock() +} + +func (registry *AddressesRegistry) Update(addedAddresses []string, removedAddresses []string) { + registry.registeredMutex.Lock() + defer registry.registeredMutex.Unlock() + registry.removedMutex.Lock() + defer registry.removedMutex.Unlock() + for _, address := range removedAddresses { + registry.removedAddresses = removeAddress(registry.removedAddresses, address) + delete(registry.registeredAddresses, address) + } + for _, address := range addedAddresses { + registry.registeredAddresses[address] = true + } +} + +func copyAddressesMap(addresses map[string]bool) map[string]bool { + addressesCopy := make(map[string]bool, len(addresses)) + for address := range addresses { + addressesCopy[address] = true + } + return addressesCopy +} + +func removeAddress(addresses []string, address string) []string { + for i := 0; i < len(addresses); i++ { + if address == addresses[i] { + return append(addresses[:i], addresses[i+1:]...) + } + } + return addresses +} diff --git a/validatornode/application/verification/addresses_registry_test.go b/validatornode/application/verification/addresses_registry_test.go new file mode 100644 index 00000000..f30d57ee --- /dev/null +++ b/validatornode/application/verification/addresses_registry_test.go @@ -0,0 +1,93 @@ +package verification + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" + "testing" +) + +func Test_Clear_ListsAreNotEmpty_ListsAreCleared(t *testing.T) { + // Arrange + registry := &AddressesRegistry{ + registeredAddresses: map[string]bool{"test": true}, + removedAddresses: []string{"test"}, + } + + // Act + registry.Clear() + + // Assert + test.Assert(t, !registry.registeredAddresses["test"], "registeredAddresses has not been correctly cleared") + test.Assert(t, registry.removedAddresses == nil, "removedAddresses has not been correctly cleared") +} + +func Test_Filter_OneOfTwoAddressesIsAlreadyRegistered_ReturnsOneAddress(t *testing.T) { + // Arrange + registry := &AddressesRegistry{ + registeredAddresses: map[string]bool{"test": true}, + } + expectedAddress := "new" + + // Act + newAddresses := registry.Filter([]string{"test", expectedAddress}) + + // Assert + actualLength := len(newAddresses) + expectedLength := 1 + test.Assert(t, actualLength == expectedLength, fmt.Sprintf("newAddresses should contain %d item whereas it contains %d", expectedLength, actualLength)) + actualAddress := newAddresses[0] + test.Assert(t, actualAddress == expectedAddress, fmt.Sprintf("newAddresses should contain %s whereas it contains %s", expectedAddress, actualAddress)) +} + +func Test_IsRegistered_Registered_ReturnsTrue(t *testing.T) { + // Arrange + registry := &AddressesRegistry{ + registeredAddresses: map[string]bool{"test": true}, + } + + // Act + isRegistered := registry.IsRegistered("test") + + // Assert + test.Assert(t, isRegistered, "address is not registered whereas it should be") +} + +func Test_IsRegistered_NotRegistered_ReturnsFalse(t *testing.T) { + // Arrange + registry := NewAddressesRegistry(nil, nil) + + // Act + isRegistered := !registry.IsRegistered("new") + + // Assert + test.Assert(t, isRegistered, "address is registered whereas it should not") +} + +func Test_RemovedAddresses_OneAddressRemoved_ReturnsOneAddress(t *testing.T) { + // Arrange + registry := &AddressesRegistry{ + removedAddresses: []string{"test"}, + } + + // Act + removedAddresses := registry.RemovedAddresses() + + // Assert + test.Assert(t, removedAddresses[0] == "test", "address is not removed whereas it should be") +} + +func Test_Update_AddingAndRemovingAddresses_AddressesCorrectlyUpdated(t *testing.T) { + // Arrange + registry := &AddressesRegistry{ + registeredAddresses: map[string]bool{"test": true}, + removedAddresses: []string{"test"}, + } + + // Act + registry.Update([]string{"new"}, []string{"test"}) + + // Assert + test.Assert(t, registry.registeredAddresses["new"], "registeredAddresses has not been correctly updated") + test.Assert(t, !registry.registeredAddresses["test"], "registeredAddresses has not been correctly updated") + test.Assert(t, len(registry.removedAddresses) == 0, "removedAddresses has not been correctly updated") +} diff --git a/src/node/protocol/verification/blockchain.go b/validatornode/application/verification/blockchain.go similarity index 50% rename from src/node/protocol/verification/blockchain.go rename to validatornode/application/verification/blockchain.go index 237615b4..0c9829fd 100644 --- a/src/node/protocol/verification/blockchain.go +++ b/validatornode/application/verification/blockchain.go @@ -4,47 +4,41 @@ import ( "encoding/json" "errors" "fmt" - "github.com/my-cloud/ruthenium/src/log" - "github.com/my-cloud/ruthenium/src/node/network" - "github.com/my-cloud/ruthenium/src/node/protocol" + "github.com/my-cloud/ruthenium/validatornode/application" "sync" "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" ) type Blockchain struct { - blocks []*Block - mutex sync.RWMutex - registeredAddresses map[string]bool - registry protocol.Registry - synchronizer network.Synchronizer - settings protocol.Settings - utxosByAddress map[string][]*Utxo - utxosById map[string][]*Utxo - logger log.Logger + blocks []*ledger.Block + mutex sync.RWMutex + registry application.AddressesManager + sendersManager application.SendersManager + utxosManager application.UtxosManager + settings application.ProtocolSettingsProvider + logger log.Logger } -func NewBlockchain(registry protocol.Registry, settings protocol.Settings, synchronizer network.Synchronizer, logger log.Logger) *Blockchain { - utxosByAddress := make(map[string][]*Utxo) - utxosById := make(map[string][]*Utxo) - registeredAddresses := make(map[string]bool) - blockchain := newBlockchain(nil, registeredAddresses, registry, settings, synchronizer, utxosByAddress, utxosById, logger) +func NewBlockchain(registry application.AddressesManager, settings application.ProtocolSettingsProvider, sendersManager application.SendersManager, utxosManager application.UtxosManager, logger log.Logger) *Blockchain { + blockchain := newBlockchain(nil, registry, settings, sendersManager, utxosManager, logger) return blockchain } -func newBlockchain(blocks []*Block, registeredAddresses map[string]bool, registry protocol.Registry, settings protocol.Settings, synchronizer network.Synchronizer, utxosByAddress map[string][]*Utxo, utxosById map[string][]*Utxo, logger log.Logger) *Blockchain { +func newBlockchain(blocks []*ledger.Block, registry application.AddressesManager, settings application.ProtocolSettingsProvider, sendersManager application.SendersManager, utxosManager application.UtxosManager, logger log.Logger) *Blockchain { blockchain := new(Blockchain) blockchain.blocks = blocks - blockchain.registeredAddresses = registeredAddresses blockchain.registry = registry blockchain.settings = settings - blockchain.synchronizer = synchronizer - blockchain.utxosByAddress = utxosByAddress - blockchain.utxosById = utxosById + blockchain.sendersManager = sendersManager + blockchain.utxosManager = utxosManager blockchain.logger = logger return blockchain } -func (blockchain *Blockchain) AddBlock(timestamp int64, transactionsBytes []byte, newAddresses []string) error { +func (blockchain *Blockchain) AddBlock(timestamp int64, transactions []*ledger.Transaction, newAddresses []string) error { blockchain.mutex.Lock() defer blockchain.mutex.Unlock() var previousHash [32]byte @@ -56,71 +50,26 @@ func (blockchain *Blockchain) AddBlock(timestamp int64, transactionsBytes []byte return fmt.Errorf("unable to calculate last block hash: %w", err) } } - var addedRegisteredAddresses []string - var removedRegisteredAddresses []string - for address := range blockchain.registeredAddresses { - isPohValid, err := blockchain.registry.IsRegistered(address) - if err != nil { - return fmt.Errorf("failed to get proof of humanity: %w", err) - } - if isPohValid { - addedRegisteredAddresses = append(addedRegisteredAddresses, address) - } else { - removedRegisteredAddresses = append(removedRegisteredAddresses, address) - } - } - for _, address := range newAddresses { - isPohValid, err := blockchain.registry.IsRegistered(address) - if err != nil { - return fmt.Errorf("failed to get proof of humanity: %w", err) - } - if isPohValid { - addedRegisteredAddresses = append(addedRegisteredAddresses, address) - } - } - var transactions []*Transaction - if transactionsBytes != nil { - err := json.Unmarshal(transactionsBytes, &transactions) - if err != nil { - return fmt.Errorf("failed to unmarshal transactions: %w", err) - } - } - block := NewBlock(previousHash, addedRegisteredAddresses, removedRegisteredAddresses, timestamp, transactions) + addedAddresses := blockchain.registry.Filter(newAddresses) + removedAddresses := blockchain.registry.RemovedAddresses() + block := ledger.NewBlock(previousHash, addedAddresses, removedAddresses, timestamp, transactions) return blockchain.addBlock(block) } -func (blockchain *Blockchain) Blocks(startingBlockHeight uint64) []byte { +func (blockchain *Blockchain) Blocks(startingBlockHeight uint64) []*ledger.Block { blockchain.mutex.RLock() defer blockchain.mutex.RUnlock() var endingBlockHeight uint64 blocksCountLimit := blockchain.settings.BlocksCountLimit() blocksCount := len(blockchain.blocks) if blockchain.isEmpty() || startingBlockHeight > uint64(blocksCount)-1 || blocksCountLimit == 0 { - return marshalledEmptyArray() + return []*ledger.Block{} } else if startingBlockHeight+blocksCountLimit < uint64(blocksCount) { endingBlockHeight = startingBlockHeight + blocksCountLimit } else { endingBlockHeight = uint64(blocksCount) } - blocks := blockchain.blocks[startingBlockHeight:endingBlockHeight] - blocksBytes, err := json.Marshal(blocks) - if err != nil { - blockchain.logger.Error(err.Error()) - return marshalledEmptyArray() - } - return blocksBytes -} - -func (blockchain *Blockchain) Copy() protocol.Blockchain { - blockchain.mutex.RLock() - defer blockchain.mutex.RUnlock() - blocks := make([]*Block, len(blockchain.blocks)) - copy(blocks, blockchain.blocks) - utxosByAddress := copyUtxosMap(blockchain.utxosByAddress) - utxosById := copyUtxosMap(blockchain.utxosById) - registeredAddresses := copyRegisteredAddressesMap(blockchain.registeredAddresses) - blockchainCopy := newBlockchain(blocks, registeredAddresses, blockchain.registry, blockchain.settings, blockchain.synchronizer, utxosByAddress, utxosById, blockchain.logger) - return blockchainCopy + return blockchain.blocks[startingBlockHeight:endingBlockHeight] } func (blockchain *Blockchain) FirstBlockTimestamp() int64 { @@ -139,19 +88,27 @@ func (blockchain *Blockchain) LastBlockTimestamp() int64 { } } +func (blockchain *Blockchain) LastBlockTransactions() []*ledger.Transaction { + if blockchain.isEmpty() { + return []*ledger.Transaction{} + } else { + return blockchain.blocks[len(blockchain.blocks)-1].Transactions() + } +} + func (blockchain *Blockchain) Update(timestamp int64) { // Verify neighbor blockchains - neighbors := blockchain.synchronizer.Neighbors() - blocksByTarget := make(map[string][]*Block) + neighbors := blockchain.sendersManager.Senders() + blocksByTarget := make(map[string][]*ledger.Block) hostBlocks := blockchain.blocks var waitGroup sync.WaitGroup var mutex sync.RWMutex if len(hostBlocks) > 2 { hostTarget := "host" blocksByTarget[hostTarget] = hostBlocks - oldHostBlocks := make([]*Block, len(hostBlocks)-1) + oldHostBlocks := make([]*ledger.Block, len(hostBlocks)-1) copy(oldHostBlocks, hostBlocks[:len(hostBlocks)-1]) - lastHostBlocks := []*Block{hostBlocks[len(hostBlocks)-1]} + lastHostBlocks := []*ledger.Block{hostBlocks[len(hostBlocks)-1]} startingBlockHeight := uint64(len(hostBlocks) - 1) for _, neighbor := range neighbors { waitGroup.Add(1) @@ -190,7 +147,7 @@ func (blockchain *Blockchain) Update(timestamp int64) { } } waitGroup.Wait() - var selectedBlocks []*Block + var selectedBlocks []*ledger.Block var isDifferent bool if len(blocksByTarget) > 0 { // Keep blockchains with consensus for the previous hash (prevent forks) @@ -279,28 +236,28 @@ func (blockchain *Blockchain) Update(timestamp int64) { } } } - if isDifferent && len(selectedBlocks) != 0 { + isReplaced := isDifferent && len(selectedBlocks) != 0 + if isReplaced { blockchain.mutex.Lock() defer blockchain.mutex.Unlock() - var newBlocks []*Block + var newBlocks []*ledger.Block if isFork { - blockchain.registeredAddresses = make(map[string]bool) - blockchain.utxosById = make(map[string][]*Utxo) - blockchain.utxosByAddress = make(map[string][]*Utxo) + blockchain.registry.Clear() + blockchain.utxosManager.Clear() newBlocks = selectedBlocks[:len(selectedBlocks)-1] } else if len(hostBlocks) < len(selectedBlocks) { newBlocks = selectedBlocks[len(hostBlocks)-1 : len(selectedBlocks)-1] } - var err error for _, newBlock := range newBlocks { - err = blockchain.updateUtxos(newBlock, newBlock.Timestamp()) - if err != nil { + if err := blockchain.utxosManager.UpdateUtxos(newBlock.Transactions(), newBlock.Timestamp()); err != nil { blockchain.logger.Error(fmt.Errorf("verification failed: failed to add UTXO: %w", err).Error()) - blockchain.logger.Debug("verification done: blockchain kept") - return + isReplaced = false + } else { + blockchain.registry.Update(newBlock.AddedRegisteredAddresses(), newBlock.RemovedRegisteredAddresses()) } - blockchain.updateRegisteredAddresses(newBlock.AddedRegisteredAddresses(), newBlock.RemovedRegisteredAddresses()) } + } + if isReplaced { blockchain.blocks = selectedBlocks blockchain.logger.Debug("verification done: blockchain replaced") } else { @@ -308,39 +265,13 @@ func (blockchain *Blockchain) Update(timestamp int64) { } } -func (blockchain *Blockchain) Utxo(input protocol.InputInfo) (protocol.Utxo, error) { - utxos, ok := blockchain.utxosById[input.TransactionId()] - if !ok || int(input.OutputIndex()) > len(utxos)-1 { - return nil, fmt.Errorf("failed to find UTXO, input: %v", input) - } - utxo := utxos[input.OutputIndex()] - if utxo == nil { - return nil, fmt.Errorf("failed to find UTXO, input: %v", input) - } - return utxo, nil -} - -func (blockchain *Blockchain) Utxos(address string) []byte { - utxos, ok := blockchain.utxosByAddress[address] - if !ok { - return marshalledEmptyArray() - } - marshaledUtxos, err := json.Marshal(utxos) - if err != nil { - blockchain.logger.Error(err.Error()) - return marshalledEmptyArray() - } - return marshaledUtxos -} - -func (blockchain *Blockchain) addBlock(block *Block) error { +func (blockchain *Blockchain) addBlock(block *ledger.Block) error { if !blockchain.isEmpty() { lastBlock := blockchain.blocks[len(blockchain.blocks)-1] - err := blockchain.updateUtxos(lastBlock, lastBlock.Timestamp()) - if err != nil { + if err := blockchain.utxosManager.UpdateUtxos(lastBlock.Transactions(), lastBlock.Timestamp()); err != nil { return fmt.Errorf("failed to add UTXO: %w", err) } - blockchain.updateRegisteredAddresses(lastBlock.AddedRegisteredAddresses(), lastBlock.RemovedRegisteredAddresses()) + blockchain.registry.Update(lastBlock.AddedRegisteredAddresses(), lastBlock.RemovedRegisteredAddresses()) } blockchain.blocks = append(blockchain.blocks, block) return nil @@ -350,118 +281,20 @@ func (blockchain *Blockchain) isEmpty() bool { return len(blockchain.blocks) == 0 } -func (blockchain *Blockchain) isRegistered(address string, addedRegisteredAddresses []string, removedRegisteredAddresses []string) error { - var isRegistered bool - for _, addedAddress := range addedRegisteredAddresses { - isRegistered = addedAddress == address - if isRegistered { - break - } - } - if !isRegistered { - for _, addedAddress := range removedRegisteredAddresses { - isRegistered = addedAddress != address - if !isRegistered { - break - } - } - if !isRegistered { - if _, ok := blockchain.registeredAddresses[address]; !ok { - return fmt.Errorf("an incomed output address is not registered") - } - } - } - return nil -} - -func (blockchain *Blockchain) updateRegisteredAddresses(addedRegisteredAddresses []string, removedRegisteredAddresses []string) { - for _, address := range removedRegisteredAddresses { - delete(blockchain.registeredAddresses, address) - } - for _, address := range addedRegisteredAddresses { - blockchain.registeredAddresses[address] = true - } -} - -func (blockchain *Blockchain) updateUtxos(block *Block, timestamp int64) error { - utxosByAddress := copyUtxosMap(blockchain.utxosByAddress) - utxosById := copyUtxosMap(blockchain.utxosById) - for _, transaction := range block.Transactions() { - utxosForTransactionId, ok := utxosById[transaction.Id()] - if ok { - return fmt.Errorf("transaction ID already exists: %s", transaction.Id()) - } - for j, output := range transaction.Outputs() { - if output.InitialValue() > 0 { - inputInfo := NewInputInfo(uint16(j), transaction.Id()) - utxo := NewUtxo(inputInfo, output, timestamp) - utxosForTransactionId = append(utxosForTransactionId, utxo) - utxosById[transaction.Id()] = utxosForTransactionId - utxosByAddress[output.Address()] = append(utxosByAddress[output.Address()], utxo) - } - } - for _, input := range transaction.Inputs() { - utxosForInputTransactionId := utxosById[input.TransactionId()] - if int(input.OutputIndex()) > len(utxosForInputTransactionId)-1 { - return fmt.Errorf("failed to find UTXO, input: %v", input) - } - utxo := utxosForInputTransactionId[input.OutputIndex()] - if utxo == nil { - return fmt.Errorf("failed to find output index, input: %v", input) - } - utxosForUtxoAddress := utxosByAddress[utxo.Address()] - utxosForUtxoAddress = removeUtxo(utxosForUtxoAddress, input.TransactionId(), input.OutputIndex()) - utxosByAddress[utxo.Address()] = utxosForUtxoAddress - utxosById[input.TransactionId()][input.OutputIndex()] = nil - isEmpty := true - for _, output := range utxosForInputTransactionId { - if output != nil { - isEmpty = false - break - } - } - if isEmpty { - delete(utxosById, input.TransactionId()) - } - if len(utxosForUtxoAddress) == 0 { - delete(utxosByAddress, utxo.Address()) - } - } - } - if err := verifyIncomes(utxosByAddress); err != nil { - return err - } - blockchain.utxosById = utxosById - blockchain.utxosByAddress = utxosByAddress - return nil -} - -func (blockchain *Blockchain) verify(lastHostBlocks []*Block, neighborBlocks []*Block, oldHostBlocks []*Block, timestamp int64) ([]*Block, error) { +func (blockchain *Blockchain) verify(lastHostBlocks []*ledger.Block, neighborBlocks []*ledger.Block, oldHostBlocks []*ledger.Block, timestamp int64) ([]*ledger.Block, error) { if len(oldHostBlocks) == 0 && len(neighborBlocks) < 2 { return nil, errors.New("neighbor's blockchain is too short") } else if len(oldHostBlocks) > 0 && (len(neighborBlocks) == 0 || lastHostBlocks[0].PreviousHash() != neighborBlocks[0].PreviousHash()) { return nil, errors.New("neighbor's blockchain is a fork") } - if neighborBlocks[len(neighborBlocks)-1].Timestamp() == timestamp { - err := blockchain.verifyLastBlock(neighborBlocks) - if err != nil { - return nil, err - } - } - var verifiedBlocks []*Block - var utxosByAddress map[string][]*Utxo - var utxosById map[string][]*Utxo - var registeredAddresses map[string]bool + neighborUtxosPool := blockchain.utxosManager.Copy() + neighborRegistry := blockchain.registry.Copy() if len(oldHostBlocks) == 0 { - utxosByAddress = make(map[string][]*Utxo) - utxosById = make(map[string][]*Utxo) - registeredAddresses = make(map[string]bool) - } else { - utxosByAddress = copyUtxosMap(blockchain.utxosByAddress) - utxosById = copyUtxosMap(blockchain.utxosById) - registeredAddresses = copyRegisteredAddressesMap(blockchain.registeredAddresses) + neighborUtxosPool.Clear() + neighborRegistry.Clear() } - neighborBlockchain := newBlockchain(oldHostBlocks, registeredAddresses, blockchain.registry, blockchain.settings, blockchain.synchronizer, utxosByAddress, utxosById, blockchain.logger) + neighborBlockchain := newBlockchain(oldHostBlocks, neighborRegistry, blockchain.settings, blockchain.sendersManager, neighborUtxosPool, blockchain.logger) + var verifiedBlocks []*ledger.Block for i := 0; i < len(neighborBlocks); i++ { neighborBlock := neighborBlocks[i] var previousBlockTimestamp int64 @@ -511,7 +344,7 @@ func (blockchain *Blockchain) verify(lastHostBlocks []*Block, neighborBlocks []* } } if isNewBlock && !isGenesisBlock { - if err := blockchain.verifyBlock(neighborBlock, previousBlockTimestamp, timestamp, neighborBlockchain); err != nil { + if err := neighborBlockchain.verifyBlock(neighborBlock, previousBlockTimestamp, timestamp); err != nil { return nil, err } } @@ -523,30 +356,27 @@ func (blockchain *Blockchain) verify(lastHostBlocks []*Block, neighborBlocks []* verifiedBlocks = append(verifiedBlocks, neighborBlock) } lastNeighborBlock := neighborBlocks[len(neighborBlocks)-1] - if err := blockchain.verifyRegisteredAddresses(lastNeighborBlock); err != nil { - return nil, fmt.Errorf("failed to verify registered addresses: %w", err) - } currentBlockTimestamp := lastNeighborBlock.Timestamp() - nextBlockTimestamp := currentBlockTimestamp + blockchain.settings.ValidationTimestamp() + nextBlockTimestamp := currentBlockTimestamp + neighborBlockchain.settings.ValidationTimestamp() if err := neighborBlockchain.AddBlock(nextBlockTimestamp, nil, nil); err != nil { return nil, err } return verifiedBlocks, nil } -func (blockchain *Blockchain) verifyNeighborBlockchain(timestamp int64, neighbor network.Neighbor, startingBlockHeight uint64, lastHostBlocks []*Block, oldHostBlocks []*Block) ([]*Block, error) { +func (blockchain *Blockchain) verifyNeighborBlockchain(timestamp int64, neighbor application.Sender, startingBlockHeight uint64, lastHostBlocks []*ledger.Block, oldHostBlocks []*ledger.Block) ([]*ledger.Block, error) { type ChanResult struct { - Blocks []*Block + Blocks []*ledger.Block Err error } blocksChannel := make(chan *ChanResult) - go func(neighbor network.Neighbor) { + go func(neighbor application.Sender) { defer close(blocksChannel) neighborBlocksBytes, err := neighbor.GetBlocks(startingBlockHeight) if err != nil { blocksChannel <- &ChanResult{Err: fmt.Errorf("failed to get neighbor's blockchain: %w", err)} } - var neighborBlocks []*Block + var neighborBlocks []*ledger.Block err = json.Unmarshal(neighborBlocksBytes, &neighborBlocks) if err != nil { blocksChannel <- &ChanResult{Err: fmt.Errorf("failed to get neighbor's blockchain: %w", err)} @@ -566,7 +396,7 @@ func (blockchain *Blockchain) verifyNeighborBlockchain(timestamp int64, neighbor } } -func (blockchain *Blockchain) verifyBlock(neighborBlock *Block, previousBlockTimestamp int64, timestamp int64, neighborBlockchain *Blockchain) error { +func (blockchain *Blockchain) verifyBlock(neighborBlock *ledger.Block, previousBlockTimestamp int64, timestamp int64) error { var rewarded bool currentBlockTimestamp := neighborBlock.Timestamp() expectedBlockTimestamp := previousBlockTimestamp + blockchain.settings.ValidationTimestamp() @@ -583,7 +413,6 @@ func (blockchain *Blockchain) verifyBlock(neighborBlock *Block, previousBlockTim var reward uint64 var totalTransactionsFees uint64 addedRegisteredAddresses := neighborBlock.AddedRegisteredAddresses() - removedRegisteredAddresses := neighborBlock.RemovedRegisteredAddresses() for _, transaction := range neighborBlock.Transactions() { if transaction.HasReward() { // Check that there is only one reward by block @@ -593,27 +422,34 @@ func (blockchain *Blockchain) verifyBlock(neighborBlock *Block, previousBlockTim rewarded = true reward = transaction.RewardValue() } else { - if err := transaction.VerifySignatures(neighborBlockchain.Utxo); err != nil { - return fmt.Errorf("neighbor transaction is invalid: %w", err) - } - fee, err := transaction.Fee(blockchain.settings, currentBlockTimestamp, neighborBlockchain.Utxo) - if err != nil { - return fmt.Errorf("failed to verify a neighbor block transaction fee: %w", err) - } - totalTransactionsFees += fee if currentBlockTimestamp < transaction.Timestamp() { return fmt.Errorf("a neighbor block transaction timestamp is too far in the future: transaction timestamp: %d, id: %s", transaction.Timestamp(), transaction.Id()) } if transaction.Timestamp() < previousBlockTimestamp { return fmt.Errorf("a neighbor block transaction timestamp is too old: transaction timestamp: %d, id: %s", transaction.Timestamp(), transaction.Id()) } + if err := transaction.VerifySignatures(); err != nil { + return fmt.Errorf("neighbor transaction is invalid: %w", err) + } for _, output := range transaction.Outputs() { if output.IsYielding() { - if err := blockchain.isRegistered(output.Address(), addedRegisteredAddresses, removedRegisteredAddresses); err != nil { - return err + var isNewlyRegistered bool + for _, addedAddress := range addedRegisteredAddresses { + isNewlyRegistered = addedAddress == output.Address() + if isNewlyRegistered { + break + } + } + if !isNewlyRegistered && !blockchain.registry.IsRegistered(output.Address()) { + return fmt.Errorf("a neighbor block transaction yielding output address is not registered") } } } + fee, err := blockchain.utxosManager.CalculateFee(transaction, currentBlockTimestamp) + if err != nil { + return fmt.Errorf("failed to verify a neighbor block transaction fee: %w", err) + } + totalTransactionsFees += fee } } if !rewarded { @@ -624,82 +460,3 @@ func (blockchain *Blockchain) verifyBlock(neighborBlock *Block, previousBlockTim } return nil } - -func (blockchain *Blockchain) verifyLastBlock(lastNeighborBlocks []*Block) error { - lastNeighborBlock := lastNeighborBlocks[len(lastNeighborBlocks)-1] - validatorAddress := lastNeighborBlock.ValidatorAddress() - isValidatorRegistered, err := blockchain.registry.IsRegistered(validatorAddress) - if err != nil { - blockchain.logger.Debug(fmt.Errorf("failed to get validator proof of humanity: %w", err).Error()) - } else if !isValidatorRegistered { - return fmt.Errorf("validator address is not registered in Proof of Humanity registry") - } - return nil -} - -func (blockchain *Blockchain) verifyRegisteredAddresses(block *Block) error { - for _, address := range block.RemovedRegisteredAddresses() { - isPohValid, err := blockchain.registry.IsRegistered(address) - if err != nil { - blockchain.logger.Debug(fmt.Errorf("failed to get proof of humanity for address %s: %w", address, err).Error()) - } else if isPohValid { - return fmt.Errorf("a removed address is registered") - } - } - for _, address := range block.AddedRegisteredAddresses() { - isPohValid, err := blockchain.registry.IsRegistered(address) - if err != nil { - blockchain.logger.Debug(fmt.Errorf("failed to get proof of humanity for address %s: %w", address, err).Error()) - } else if !isPohValid { - return fmt.Errorf("an added address is not registered") - } - } - return nil -} - -func copyRegisteredAddressesMap(registeredAddresses map[string]bool) map[string]bool { - registeredAddressesCopy := make(map[string]bool, len(registeredAddresses)) - for address := range registeredAddresses { - registeredAddressesCopy[address] = true - } - return registeredAddressesCopy -} - -func copyUtxosMap(utxosMap map[string][]*Utxo) map[string][]*Utxo { - utxosMapCopy := make(map[string][]*Utxo, len(utxosMap)) - for address, utxos := range utxosMap { - utxosCopy := make([]*Utxo, len(utxos)) - copy(utxosCopy, utxos) - utxosMapCopy[address] = utxosCopy - } - return utxosMapCopy -} - -func marshalledEmptyArray() []byte { - return []byte{91, 93} -} - -func removeUtxo(utxos []*Utxo, transactionId string, outputIndex uint16) []*Utxo { - for i := 0; i < len(utxos); i++ { - if utxos[i].TransactionId() == transactionId && utxos[i].OutputIndex() == outputIndex { - utxos = append(utxos[:i], utxos[i+1:]...) - return utxos - } - } - return utxos -} - -func verifyIncomes(utxosByAddress map[string][]*Utxo) error { - for address, utxos := range utxosByAddress { - var isYielding bool - for _, utxo := range utxos { - if utxo.IsYielding() { - if isYielding { - return fmt.Errorf("income requested for several UTXOs for address: %s", address) - } - isYielding = true - } - } - } - return nil -} diff --git a/validatornode/application/verification/blockchain_test.go b/validatornode/application/verification/blockchain_test.go new file mode 100644 index 00000000..6b6861ff --- /dev/null +++ b/validatornode/application/verification/blockchain_test.go @@ -0,0 +1,1022 @@ +package verification + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "testing" + "time" + + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +const ( + blockchainReplacedMessage = "verification done: blockchain replaced" + blockchainKeptMessage = "verification done: blockchain kept" +) + +func Test_AddBlock_ValidParameters_NoErrorReturned(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + + // Act + err := blockchain.AddBlock(0, nil, nil) + + // Assert + test.Assert(t, err == nil, "error is returned whereas it should not") +} + +func Test_Blocks_BlocksCountLimitSetToZero_ReturnsEmptyArray(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return 0 } + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + + // Act + blocks := blockchain.Blocks(0) + + // Assert + test.Assert(t, len(blocks) == 0, "blocks should be empty") +} + +func Test_Blocks_BlocksCountLimitSetToOne_ReturnsOneBlock(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + var expectedBlocksCount uint64 = 1 + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return expectedBlocksCount } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var validationInterval int64 = 1 + var genesisTimestamp int64 = 0 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + _ = blockchain.AddBlock(genesisTimestamp+validationInterval, nil, nil) + + // Act + blocks := blockchain.Blocks(0) + + // Assert + actualBlocksCount := uint64(len(blocks)) + test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) +} + +func Test_Blocks_BlocksCountLimitSetToTwo_ReturnsTwoBlocks(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + var expectedBlocksCount uint64 = 2 + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return expectedBlocksCount } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var validationInterval int64 = 1 + var genesisTimestamp int64 = 0 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + _ = blockchain.AddBlock(genesisTimestamp+validationInterval, nil, nil) + + // Act + blocks := blockchain.Blocks(0) + + // Assert + actualBlocksCount := uint64(len(blocks)) + test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) +} + +func Test_Blocks_StartingBlockHeightGreaterThanBlocksLength_ReturnsEmptyArray(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + var blocksCount uint64 = 1 + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return blocksCount } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var genesisTimestamp int64 = 0 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + + // Act + blocks := blockchain.Blocks(1) + + // Assert + expectedBlocksCount := 0 + actualBlocksCount := len(blocks) + test.Assert(t, actualBlocksCount == expectedBlocksCount, fmt.Sprintf("blocks count is %d whereas it should be %d", actualBlocksCount, expectedBlocksCount)) +} + +func Test_FirstBlockTimestamp_BlockchainIsEmpty_Returns0(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + + // Act + actualTimestamp := blockchain.FirstBlockTimestamp() + + // Assert + var expectedTimestamp int64 = 0 + test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) +} + +func Test_FirstBlockTimestamp_BlockchainIsNotEmpty_ReturnsFirstBlockTimestamp(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var genesisTimestamp int64 = 0 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + + // Act + actualTimestamp := blockchain.FirstBlockTimestamp() + + // Assert + expectedTimestamp := genesisTimestamp + test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) +} + +func Test_LastBlockTimestamp_BlockchainIsEmpty_Returns0(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + + // Act + actualTimestamp := blockchain.LastBlockTimestamp() + + // Assert + var expectedTimestamp int64 = 0 + test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) +} + +func Test_LastBlockTimestamp_BlockchainIsNotEmpty_ReturnsLastBlockTimestamp(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var genesisTimestamp int64 = 0 + var expectedTimestamp int64 = 1 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + _ = blockchain.AddBlock(expectedTimestamp, nil, nil) + + // Act + actualTimestamp := blockchain.LastBlockTimestamp() + + // Assert + test.Assert(t, actualTimestamp == expectedTimestamp, fmt.Sprintf("timestamp is %d whereas it should be %d", actualTimestamp, expectedTimestamp)) +} + +func Test_LastBlockTransactions_BlockchainIsEmpty_ReturnsEmptyArray(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + + // Act + actualTransactions := blockchain.LastBlockTransactions() + + // Assert + expectedTransactionsLength := 0 + test.Assert(t, len(actualTransactions) == expectedTransactionsLength, fmt.Sprintf("transactions length is %d whereas it should be %d", len(actualTransactions), expectedTransactionsLength)) +} + +func Test_LastBlockTransactions_BlockchainIsNotEmpty_ReturnsLastBlockTimestamp(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + sendersManagerMock := new(application.SendersManagerMock) + settings := new(application.ProtocolSettingsProviderMock) + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + var genesisTimestamp int64 = 0 + var timestamp int64 = 1 + _ = blockchain.AddBlock(genesisTimestamp, nil, nil) + transaction, _ := ledger.NewRewardTransaction("", false, timestamp, 0) + expectedTransactionId := transaction.Id() + _ = blockchain.AddBlock(timestamp, []*ledger.Transaction{transaction}, nil) + + // Act + actualTransactions := blockchain.LastBlockTransactions() + + // Assert + actualTransactionId := actualTransactions[0].Id() + test.Assert(t, actualTransactionId == expectedTransactionId, fmt.Sprintf("transactions ID is %s whereas it should be %s", actualTransactionId, expectedTransactionId)) +} + +func Test_Update_NeighborBlockchainIsBetter_IsReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + var validationTimestamp int64 = 11 + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return 2 } + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + now := 5 * validationTimestamp + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.ClearFunc = func() {} + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(now-5*validationTimestamp, nil, nil) + _ = blockchain.AddBlock(now-4*validationTimestamp, nil, nil) + blocks := blockchain.Blocks(0) + genesisBlockHash := blocks[1].PreviousHash() + block1 := ledger.NewRewardedBlock(genesisBlockHash, now-4*validationTimestamp) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-3*validationTimestamp) + hash2, _ := block2.Hash() + block3 := ledger.NewRewardedBlock(hash2, now-2*validationTimestamp) + hash3, _ := block3.Hash() + block4 := ledger.NewRewardedBlock(hash3, now-validationTimestamp) + neighborBlocks := []*ledger.Block{blocks[0], block1, block2, block3, block4} + neighborBlocksBytes, _ := json.Marshal(neighborBlocks) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + return neighborBlocksBytes, nil + } + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + blockchainReplacedMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborNewBlockTimestampIsInvalid_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return 1 } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + type args struct { + firstBlockTimestamp int64 + secondBlockTimestamp int64 + } + tests := []struct { + name string + args args + want []int + }{ + { + name: "SecondTimestampBeforeTheFirstOne", + args: args{ + firstBlockTimestamp: 1, + secondBlockTimestamp: 0, + }, + }, + { + name: "BlockMissing", + args: args{ + firstBlockTimestamp: 0, + secondBlockTimestamp: 2, + }, + }, + { + name: "SameZeroedTimestamp", + args: args{ + firstBlockTimestamp: 0, + secondBlockTimestamp: 0, + }, + }, + { + name: "SameNonZeroTimestamp", + args: args{ + firstBlockTimestamp: 1, + secondBlockTimestamp: 1, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + block1 := ledger.NewRewardedBlock([32]byte{}, tt.args.firstBlockTimestamp) + hash, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash, tt.args.secondBlockTimestamp) + blocks := []*ledger.Block{block1, block2} + blockBytes, _ := json.Marshal(blocks) + return blockBytes, nil + } + + // Act + blockchain.Update(1) + + // Assert + expectedMessages := []string{ + "neighbor block timestamp is invalid", + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) + }) + } +} + +func Test_Update_NeighborNewBlockTimestampIsInTheFuture_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + var validationTimestamp int64 = 1 + now := validationTimestamp + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + block1 := ledger.NewRewardedBlock([32]byte{}, now) + hash, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash, now+validationTimestamp) + blocks := []*ledger.Block{block1, block2} + blockBytes, _ := json.Marshal(blocks) + return blockBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + "neighbor block timestamp is in the future", + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborNewBlockTransactionFeeCalculationFails_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + address := test.Address + invalidTransactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var incomeLimit uint64 = 1 + genesisAmount := 2 * incomeLimit + block1 := ledger.NewGenesisBlock(address, genesisAmount) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + genesisTransaction := block1.Transactions()[0] + var genesisOutputIndex uint16 = 0 + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, invalidTransactionFee, genesisOutputIndex, "A", privateKey, publicKey, now, genesisTransaction.Id(), genesisAmount, false) + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 1) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, []string{address}, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { + if transaction.Id() == invalidTransaction.Id() { + return 0, errors.New("") + } else { + return 0, nil + } + } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + "failed to verify a neighbor block transaction fee", + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborNewBlockTransactionTimestampIsTooFarInTheFuture_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + address := test.Address + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + block1 := ledger.NewGenesisBlock(address, genesisAmount) + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey, publicKey, now+validationTimestamp, genesisTransaction.Id(), genesisAmount, false) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, []string{address}, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + fmt.Sprintf("a neighbor block transaction timestamp is too far in the future: transaction timestamp: %d, id: %s", invalidTransaction.Timestamp(), invalidTransaction.Id()), + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborNewBlockTransactionTimestampIsTooOld_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + address := test.Address + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + block1 := ledger.NewGenesisBlock(address, genesisAmount) + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey, publicKey, now-validationTimestamp-1, genesisTransaction.Id(), genesisAmount, false) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, []string{address}, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + fmt.Sprintf("a neighbor block transaction timestamp is too old: transaction timestamp: %d, id: %s", invalidTransaction.Timestamp(), invalidTransaction.Id()), + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborNewBlockTransactionInputSignatureIsInvalid_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + address := test.Address + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + privateKey2, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey2) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + block1 := ledger.NewGenesisBlock(address, genesisAmount) + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, "A", privateKey2, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, false) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, []string{address}, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + "neighbor transaction is invalid: failed to verify signature of an input: signature is invalid", + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborBlockYieldingOutputAddressIsRegistered_IsReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + address := test.Address + block1 := ledger.NewGenesisBlock(address, genesisAmount) + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, address, privateKey, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, true) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, nil, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + blockchainReplacedMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborBlockYieldingOutputAddressHasBeenRecentlyAdded_IsReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return false } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + address := test.Address + block1 := ledger.NewGenesisBlock(address, genesisAmount) + addedAddress := test.Address2 + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, addedAddress, privateKey, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, true) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, []string{addedAddress}, nil, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + blockchainReplacedMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborBlockYieldingOutputIsNotRegistered_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.ClearFunc = func() {} + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return false } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + transactionFee := 0 + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + var genesisAmount uint64 = 1 + address := test.Address + block1 := ledger.NewGenesisBlock(address, genesisAmount) + removedAddress := test.Address2 + var genesisOutputIndex uint16 = 0 + genesisTransaction := block1.Transactions()[0] + invalidTransaction := ledger.NewSignedTransaction(genesisAmount, transactionFee, genesisOutputIndex, removedAddress, privateKey, publicKey, now-validationTimestamp, genesisTransaction.Id(), genesisAmount, true) + hash1, _ := block1.Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + rewardTransaction, _ := ledger.NewRewardTransaction(address, false, now, 0) + transactions := []*ledger.Transaction{ + invalidTransaction, + rewardTransaction, + } + block3 := ledger.NewBlock(hash2, nil, []string{removedAddress}, now, transactions) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + blocks := []*ledger.Block{block1, block2, block3} + blocksBytes, _ := json.Marshal(blocks) + return blocksBytes, nil + } + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.ClearFunc = func() {} + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + _ = blockchain.AddBlock(0, nil, nil) + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + "neighbor block transaction yielding output address is not registered", + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborValidatorIsNotTheOldest_IsNotReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + rewardTransaction1, _ := ledger.NewRewardTransaction(test.Address, false, now-2*validationTimestamp, 0) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + _ = blockchain.AddBlock(now-2*validationTimestamp, []*ledger.Transaction{rewardTransaction1}, nil) + blocks := blockchain.Blocks(0) + rewardTransaction2, _ := ledger.NewRewardTransaction(test.Address, false, now-validationTimestamp, 0) + _ = blockchain.AddBlock(now-validationTimestamp, []*ledger.Transaction{rewardTransaction2}, nil) + rewardTransaction3, _ := ledger.NewRewardTransaction(test.Address, false, now, 0) + _ = blockchain.AddBlock(now, []*ledger.Transaction{rewardTransaction3}, nil) + hash1, _ := blocks[0].Hash() + block2 := ledger.NewRewardedBlock(hash1, now-validationTimestamp) + hash2, _ := block2.Hash() + block3 := ledger.NewRewardedBlock(hash2, now) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + neighborBlocks := []*ledger.Block{block3} + neighborBlocksBytes, _ := json.Marshal(neighborBlocks) + return neighborBlocksBytes, nil + } + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + blockchainKeptMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} + +func Test_Update_NeighborValidatorIsTheOldest_IsReplaced(t *testing.T) { + // Arrange + registryMock := new(application.AddressesManagerMock) + registryMock.CopyFunc = func() application.AddressesManager { return registryMock } + registryMock.FilterFunc = func([]string) []string { return nil } + registryMock.IsRegisteredFunc = func(string) bool { return true } + registryMock.RemovedAddressesFunc = func() []string { return nil } + registryMock.UpdateFunc = func([]string, []string) {} + logger := log.NewLoggerMock() + senderMock := new(application.SenderMock) + var validationTimestamp int64 = 1 + now := 2 * validationTimestamp + senderMock.TargetFunc = func() string { + return "neighbor" + } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.SendersFunc = func() []application.Sender { + return []application.Sender{senderMock} + } + settings := new(application.ProtocolSettingsProviderMock) + settings.BlocksCountLimitFunc = func() uint64 { return 2 } + settings.ValidationTimestampFunc = func() int64 { return validationTimestamp } + settings.ValidationTimeoutFunc = func() time.Duration { return time.Second } + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.CopyFunc = func() application.UtxosManager { return utxosManagerMock } + utxosManagerMock.UpdateUtxosFunc = func([]*ledger.Transaction, int64) error { return nil } + blockchain := NewBlockchain(registryMock, settings, sendersManagerMock, utxosManagerMock, logger) + rewardTransaction1, _ := ledger.NewRewardTransaction(test.Address, false, now-2*validationTimestamp, 0) + utxosManagerMock.CalculateFeeFunc = func(transaction *ledger.Transaction, timestamp int64) (uint64, error) { return 0, nil } + _ = blockchain.AddBlock(now-2*validationTimestamp, []*ledger.Transaction{rewardTransaction1}, nil) + rewardTransaction2, _ := ledger.NewRewardTransaction(test.Address, false, now-validationTimestamp, 0) + _ = blockchain.AddBlock(now-validationTimestamp, []*ledger.Transaction{rewardTransaction2}, nil) + blocks := blockchain.Blocks(0) + rewardTransaction3, _ := ledger.NewRewardTransaction(test.Address, false, now, 0) + _ = blockchain.AddBlock(now, []*ledger.Transaction{rewardTransaction3}, nil) + hash2, _ := blocks[1].Hash() + block3 := ledger.NewRewardedBlock(hash2, now) + senderMock.GetBlocksFunc = func(uint64) ([]byte, error) { + neighborBlocks := []*ledger.Block{block3} + neighborBlocksBytes, _ := json.Marshal(neighborBlocks) + return neighborBlocksBytes, nil + } + + // Act + blockchain.Update(now) + + // Assert + expectedMessages := []string{ + blockchainReplacedMessage, + } + test.AssertThatMessageIsLogged(t, logger.DebugCalls(), expectedMessages...) +} diff --git a/validatornode/application/verification/humans_manager.go b/validatornode/application/verification/humans_manager.go new file mode 100644 index 00000000..07065e11 --- /dev/null +++ b/validatornode/application/verification/humans_manager.go @@ -0,0 +1,5 @@ +package verification + +type HumansManager interface { + IsRegistered(address string) (isRegistered bool, err error) +} diff --git a/test/node/protocol/protocoltest/registry_mock.go b/validatornode/application/verification/humans_manager_mock.go similarity index 55% rename from test/node/protocol/protocoltest/registry_mock.go rename to validatornode/application/verification/humans_manager_mock.go index ff7f67cc..73a4041b 100644 --- a/test/node/protocol/protocoltest/registry_mock.go +++ b/validatornode/application/verification/humans_manager_mock.go @@ -1,33 +1,32 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package protocoltest +package verification import ( - "github.com/my-cloud/ruthenium/src/node/protocol" "sync" ) -// Ensure, that RegistryMock does implement Registry. +// Ensure, that HumansManagerMock does implement HumansManager. // If this is not the case, regenerate this file with moq. -var _ protocol.Registry = &RegistryMock{} +var _ HumansManager = &HumansManagerMock{} -// RegistryMock is a mock implementation of Registry. +// HumansManagerMock is a mock implementation of HumansManager. // -// func TestSomethingThatUsesRegistry(t *testing.T) { +// func TestSomethingThatUsesHumansManager(t *testing.T) { // -// // make and configure a mocked Registry -// mockedRegistry := &RegistryMock{ -// IsRegisteredFunc: func(address string) (bool, error) { -// panic("mock out the IsRegistered method") -// }, -// } +// // make and configure a mocked HumansManager +// mockedHumansManager := &HumansManagerMock{ +// IsRegisteredFunc: func(address string) (bool, error) { +// panic("mock out the IsRegistered method") +// }, +// } // -// // use mockedRegistry in code that requires Registry -// // and then make assertions. +// // use mockedHumansManager in code that requires HumansManager +// // and then make assertions. // -// } -type RegistryMock struct { +// } +type HumansManagerMock struct { // IsRegisteredFunc mocks the IsRegistered method. IsRegisteredFunc func(address string) (bool, error) @@ -43,9 +42,9 @@ type RegistryMock struct { } // IsRegistered calls IsRegisteredFunc. -func (mock *RegistryMock) IsRegistered(address string) (bool, error) { +func (mock *HumansManagerMock) IsRegistered(address string) (bool, error) { if mock.IsRegisteredFunc == nil { - panic("RegistryMock.IsRegisteredFunc: method is nil but Registry.IsRegistered was just called") + panic("HumansManagerMock.IsRegisteredFunc: method is nil but HumansManager.IsRegistered was just called") } callInfo := struct { Address string @@ -60,8 +59,9 @@ func (mock *RegistryMock) IsRegistered(address string) (bool, error) { // IsRegisteredCalls gets all the calls that were made to IsRegistered. // Check the length with: -// len(mockedRegistry.IsRegisteredCalls()) -func (mock *RegistryMock) IsRegisteredCalls() []struct { +// +// len(mockedHumansManager.IsRegisteredCalls()) +func (mock *HumansManagerMock) IsRegisteredCalls() []struct { Address string } { var calls []struct { diff --git a/validatornode/application/verification/utxos_registry.go b/validatornode/application/verification/utxos_registry.go new file mode 100644 index 00000000..465a61ac --- /dev/null +++ b/validatornode/application/verification/utxos_registry.go @@ -0,0 +1,191 @@ +package verification + +import ( + "errors" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "sync" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" +) + +type utxosRegistrationInfo struct { + address string + transactionId string + utxos []*ledger.Utxo +} + +type UtxosRegistry struct { + mutex sync.RWMutex + settings application.ProtocolSettingsProvider + utxosByAddress map[string][]*ledger.Utxo + utxosById map[string][]*ledger.Utxo +} + +func NewUtxosRegistry(settings application.ProtocolSettingsProvider, initialUtxos ...utxosRegistrationInfo) *UtxosRegistry { + registry := &UtxosRegistry{} + registry.settings = settings + registry.utxosByAddress = make(map[string][]*ledger.Utxo) + registry.utxosById = make(map[string][]*ledger.Utxo) + for _, info := range initialUtxos { + registry.utxosByAddress[info.address] = info.utxos + registry.utxosById[info.transactionId] = info.utxos + } + return registry +} + +func (registry *UtxosRegistry) CalculateFee(transaction *ledger.Transaction, timestamp int64) (uint64, error) { + var inputsValue uint64 + var outputsValue uint64 + for _, input := range transaction.Inputs() { + utxos, ok := registry.utxosById[input.TransactionId()] + if !ok { + return 0, fmt.Errorf("failed to find transaction ID, input: %v", input) + } + var utxo *ledger.Utxo = nil + if int(input.OutputIndex()) < len(utxos) { + utxo = utxos[input.OutputIndex()] + } + if utxo == nil { + return 0, fmt.Errorf("failed to find output index, input: %v", input) + } + utxoAddress := utxo.Address() + inputAddress := input.Address() + if utxoAddress != inputAddress { + return 0, fmt.Errorf("failed to verify input recipient address, input: %v", input) + } + value := utxo.Value(timestamp, registry.settings.HalfLifeInNanoseconds(), registry.settings.IncomeBase(), registry.settings.IncomeLimit()) + inputsValue += value + } + for _, output := range transaction.Outputs() { + outputsValue += output.InitialValue() + } + if inputsValue < outputsValue { + return 0, errors.New("fee is negative") + } + fee := inputsValue - outputsValue + minimalTransactionFee := registry.settings.MinimalTransactionFee() + if fee < minimalTransactionFee { + return 0, fmt.Errorf("fee is too low, fee: %d, minimal fee: %d", fee, minimalTransactionFee) + } + return fee, nil +} + +func (registry *UtxosRegistry) Clear() { + registry.mutex.Lock() + defer registry.mutex.Unlock() + registry.utxosByAddress = make(map[string][]*ledger.Utxo) + registry.utxosById = make(map[string][]*ledger.Utxo) +} + +func (registry *UtxosRegistry) Copy() application.UtxosManager { + registryCopy := &UtxosRegistry{} + registryCopy.settings = registry.settings + registry.mutex.Lock() + defer registry.mutex.Unlock() + registryCopy.utxosByAddress = copyUtxosMap(registry.utxosByAddress) + registryCopy.utxosById = copyUtxosMap(registry.utxosById) + return registryCopy +} + +func (registry *UtxosRegistry) UpdateUtxos(transactions []*ledger.Transaction, timestamp int64) error { + utxosByAddress := copyUtxosMap(registry.utxosByAddress) + utxosById := copyUtxosMap(registry.utxosById) + for _, transaction := range transactions { + utxosForTransactionId, ok := utxosById[transaction.Id()] + if ok { + return fmt.Errorf("transaction ID already exists: %s", transaction.Id()) + } + if len(transaction.Outputs()) > 1 || transaction.Outputs()[0].InitialValue() > 0 || transaction.Outputs()[0].IsYielding() { + for j, output := range transaction.Outputs() { + inputInfo := ledger.NewInputInfo(uint16(j), transaction.Id()) + utxo := ledger.NewUtxo(inputInfo, output, timestamp) + utxosForTransactionId = append(utxosForTransactionId, utxo) + utxosById[transaction.Id()] = utxosForTransactionId + utxosByAddress[output.Address()] = append(utxosByAddress[output.Address()], utxo) + } + } + for _, input := range transaction.Inputs() { + utxosForInputTransactionId, ok := utxosById[input.TransactionId()] + if !ok { + return fmt.Errorf("failed to find transaction ID, input: %v", input) + } + var utxo *ledger.Utxo = nil + if int(input.OutputIndex()) < len(utxosForInputTransactionId) { + utxo = utxosForInputTransactionId[input.OutputIndex()] + } + if utxo == nil { + return fmt.Errorf("failed to find output index, input: %v", input) + } + utxosForUtxoAddress := utxosByAddress[utxo.Address()] + utxosForUtxoAddress = removeUtxo(utxosForUtxoAddress, input.TransactionId(), input.OutputIndex()) + utxosByAddress[utxo.Address()] = utxosForUtxoAddress + utxosById[input.TransactionId()][input.OutputIndex()] = nil + isEmpty := true + for _, output := range utxosForInputTransactionId { + if output != nil && (output.InitialValue() > 0 || output.IsYielding()) { + isEmpty = false + break + } + } + if isEmpty { + delete(utxosById, input.TransactionId()) + } + if len(utxosForUtxoAddress) == 0 { + delete(utxosByAddress, utxo.Address()) + } + } + } + if err := verifyIncomes(utxosByAddress); err != nil { + return err + } + registry.mutex.Lock() + defer registry.mutex.Unlock() + registry.utxosById = utxosById + registry.utxosByAddress = utxosByAddress + return nil +} + +func (registry *UtxosRegistry) Utxos(address string) []*ledger.Utxo { + utxos, ok := registry.utxosByAddress[address] + if ok { + return utxos + } else { + return []*ledger.Utxo{} + } +} + +func copyUtxosMap(utxosMap map[string][]*ledger.Utxo) map[string][]*ledger.Utxo { + utxosMapCopy := make(map[string][]*ledger.Utxo, len(utxosMap)) + for address, utxos := range utxosMap { + utxosCopy := make([]*ledger.Utxo, len(utxos)) + copy(utxosCopy, utxos) + utxosMapCopy[address] = utxosCopy + } + return utxosMapCopy +} + +func removeUtxo(utxos []*ledger.Utxo, transactionId string, outputIndex uint16) []*ledger.Utxo { + for i := 0; i < len(utxos); i++ { + if utxos[i].TransactionId() == transactionId && utxos[i].OutputIndex() == outputIndex { + utxos = append(utxos[:i], utxos[i+1:]...) + return utxos + } + } + return utxos +} + +func verifyIncomes(utxosByAddress map[string][]*ledger.Utxo) error { + for address, utxos := range utxosByAddress { + var isYielding bool + for _, utxo := range utxos { + if utxo.IsYielding() { + if isYielding { + return fmt.Errorf("income requested for several UTXOs for address: %s", address) + } + isYielding = true + } + } + } + return nil +} diff --git a/validatornode/application/verification/utxos_registry_test.go b/validatornode/application/verification/utxos_registry_test.go new file mode 100644 index 00000000..8a2c8bb7 --- /dev/null +++ b/validatornode/application/verification/utxos_registry_test.go @@ -0,0 +1,230 @@ +package verification + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" + "testing" +) + +func Test_CalculateFee_UnknownTransactionId_ReturnsError(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + "", + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput("", false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, 0, 0, "", privateKey, publicKey, 0, "unknown", 1, false) + + // Act + _, err := registry.CalculateFee(transaction, 0) + + // Assert + if err == nil { + test.Assert(t, false, "error was nil whereas it should not") + return + } else { + test.AssertThatMessageIsLogged(t, []struct{ Msg string }{{Msg: err.Error()}}, "failed to find transaction ID") + } +} + +func Test_CalculateFee_UnknownOutputIndex_ReturnsError(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + "", + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput("", false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, 0, 1, "", privateKey, publicKey, 0, transactionId, 1, false) + + // Act + _, err := registry.CalculateFee(transaction, 0) + + // Assert + if err == nil { + test.Assert(t, false, "error was nil whereas it should not") + return + } else { + test.AssertThatMessageIsLogged(t, []struct{ Msg string }{{Msg: err.Error()}}, "failed to find output index") + } +} + +func Test_CalculateFee_WrongRecipientAddress_ReturnsError(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + "", + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput("", false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, 0, 0, "", privateKey, publicKey, 0, transactionId, 1, false) + + // Act + _, err := registry.CalculateFee(transaction, 0) + + // Assert + if err == nil { + test.Assert(t, false, "error was nil whereas it should not") + return + } else { + test.AssertThatMessageIsLogged(t, []struct{ Msg string }{{Msg: err.Error()}}, "failed to verify input recipient address") + } +} + +func Test_CalculateFee_FeeIsNegative_ReturnsError(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + address := publicKey.Address() + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + address, + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput(address, false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + settingsMock.HalfLifeInNanosecondsFunc = func() float64 { return 1 } + settingsMock.IncomeBaseFunc = func() uint64 { return 1 } + settingsMock.IncomeLimitFunc = func() uint64 { return 1 } + settingsMock.MinimalTransactionFeeFunc = func() uint64 { return 1 } + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, -1, 0, "", privateKey, publicKey, 0, transactionId, 1, false) + + // Act + _, err := registry.CalculateFee(transaction, 0) + + // Assert + if err == nil { + test.Assert(t, false, "error was nil whereas it should not") + return + } else { + test.AssertThatMessageIsLogged(t, []struct{ Msg string }{{Msg: err.Error()}}, "fee is negative") + } +} + +func Test_CalculateFee_FeeIsTooLow_ReturnsError(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + address := publicKey.Address() + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + address, + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput(address, false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + settingsMock.HalfLifeInNanosecondsFunc = func() float64 { return 1 } + settingsMock.IncomeBaseFunc = func() uint64 { return 1 } + settingsMock.IncomeLimitFunc = func() uint64 { return 1 } + settingsMock.MinimalTransactionFeeFunc = func() uint64 { return 1 } + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, 0, 0, "", privateKey, publicKey, 0, transactionId, 1, false) + + // Act + _, err := registry.CalculateFee(transaction, 0) + + // Assert + if err == nil { + test.Assert(t, false, "error was nil whereas it should not") + return + } else { + test.AssertThatMessageIsLogged(t, []struct{ Msg string }{{Msg: err.Error()}}, "fee is too low") + } +} + +func Test_CalculateFee_ValidTransaction_ReturnsFee(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + address := publicKey.Address() + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + address, + transactionId, + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput(address, false, 1), 0)}, + } + settingsMock := new(application.ProtocolSettingsProviderMock) + settingsMock.HalfLifeInNanosecondsFunc = func() float64 { return 1 } + settingsMock.IncomeBaseFunc = func() uint64 { return 1 } + settingsMock.IncomeLimitFunc = func() uint64 { return 1 } + settingsMock.MinimalTransactionFeeFunc = func() uint64 { return 0 } + registry := NewUtxosRegistry(settingsMock, initialUtxos) + transaction := ledger.NewSignedTransaction(1, 1, 0, "", privateKey, publicKey, 0, transactionId, 0, false) + + // Act + actualFee, _ := registry.CalculateFee(transaction, 0) + + // Assert + var expectedFee uint64 = 1 + test.Assert(t, actualFee == expectedFee, fmt.Sprintf("fee is %d whereas it should be %d", actualFee, expectedFee)) +} + +func Test_UpdateUtxos_ValidTransactions_ReturnsNil(t *testing.T) { + // Arrange + privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + publicKey := encryption.NewPublicKey(privateKey) + address := publicKey.Address() + transactionId := "" + initialUtxos := utxosRegistrationInfo{ + address, + transactionId, + []*ledger.Utxo{ledger.NewUtxo(ledger.NewInputInfo(0, ""), ledger.NewOutput(address, false, 1), 0)}, + } + registry := NewUtxosRegistry(new(application.ProtocolSettingsProviderMock), initialUtxos) + transaction := ledger.NewSignedTransaction(1, 1, 0, address, privateKey, publicKey, 0, transactionId, 0, false) + + // Act + err := registry.UpdateUtxos([]*ledger.Transaction{transaction}, 0) + + // Assert + test.Assert(t, err == nil, fmt.Errorf("error should be nil but was: %w", err).Error()) +} + +func Test_Utxos_UnknownAddress_ReturnsEmptyArray(t *testing.T) { + // Arrange + registry := NewUtxosRegistry(new(application.ProtocolSettingsProviderMock)) + + // Act + actualUtxos := registry.Utxos("") + + // Assert + actualUtxosLength := len(actualUtxos) + expectedUtxosLength := 0 + test.Assert(t, actualUtxosLength == expectedUtxosLength, fmt.Sprintf("utxos length is %d whereas it should be %d", actualUtxosLength, expectedUtxosLength)) +} + +func Test_Utxos_OneCorrespondingUtxo_ReturnsArrayWithOneUtxo(t *testing.T) { + // Arrange + address := "" + initialUtxos := utxosRegistrationInfo{ + address, + "", + []*ledger.Utxo{ledger.NewUtxo(nil, ledger.NewOutput(address, false, 1), 0)}, + } + registry := NewUtxosRegistry(new(application.ProtocolSettingsProviderMock), initialUtxos) + + // Act + actualUtxos := registry.Utxos("") + + // Assert + actualUtxosLength := len(actualUtxos) + expectedUtxosLength := 1 + test.Assert(t, actualUtxosLength == expectedUtxosLength, fmt.Sprintf("utxo length is %d whereas it should be %d", actualUtxosLength, expectedUtxosLength)) +} diff --git a/src/node/clock/tick/engine.go b/validatornode/domain/clock/engine.go similarity index 85% rename from src/node/clock/tick/engine.go rename to validatornode/domain/clock/engine.go index 437d07f8..f9c86acf 100644 --- a/src/node/clock/tick/engine.go +++ b/validatornode/domain/clock/engine.go @@ -1,15 +1,14 @@ -package tick +package clock import ( + "github.com/my-cloud/ruthenium/validatornode/application" "time" - - "github.com/my-cloud/ruthenium/src/node/clock" ) type Engine struct { function func(timestamp int64) - watch clock.Watch + watch application.TimeProvider timer time.Duration subTimer time.Duration ticker *time.Ticker @@ -19,7 +18,7 @@ type Engine struct { requested bool } -func NewEngine(function func(timestamp int64), watch clock.Watch, timer time.Duration, occurrences int64, skippedOccurrences int) *Engine { +func NewEngine(function func(timestamp int64), watch application.TimeProvider, timer time.Duration, occurrences int64, skippedOccurrences int) *Engine { var subTimer time.Duration if occurrences > 0 { subTimer = time.Duration(timer.Nanoseconds() / occurrences) @@ -30,7 +29,7 @@ func NewEngine(function func(timestamp int64), watch clock.Watch, timer time.Dur return &Engine{function, watch, timer, subTimer, ticker, occurrences, skippedOccurrences, false, false} } -func (engine *Engine) Do() { +func (engine *Engine) Pulse() { if engine.started || engine.requested { return } diff --git a/test/node/clock/tick/engine_test.go b/validatornode/domain/clock/engine_test.go similarity index 58% rename from test/node/clock/tick/engine_test.go rename to validatornode/domain/clock/engine_test.go index 629464b4..5149479b 100644 --- a/test/node/clock/tick/engine_test.go +++ b/validatornode/domain/clock/engine_test.go @@ -1,24 +1,23 @@ -package tick +package clock import ( "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" "testing" "time" - "github.com/my-cloud/ruthenium/src/node/clock/tick" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) func Test_Do_NoError_FunctionCalled(t *testing.T) { // Arrange - watchMock := new(clocktest.WatchMock) + watchMock := new(application.TimeProviderMock) watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } var calls int - engine := tick.NewEngine(func(int64) { calls++ }, watchMock, 1, 0, 0) + engine := NewEngine(func(int64) { calls++ }, watchMock, 1, 0, 0) // Act - engine.Do() + engine.Pulse() // Assert test.Assert(t, calls == 1, fmt.Sprintf("The function is called %d times whereas it should be called once.", calls)) @@ -26,11 +25,11 @@ func Test_Do_NoError_FunctionCalled(t *testing.T) { func Test_Start_NotStarted_Started(t *testing.T) { // Arrange - watchMock := new(clocktest.WatchMock) + watchMock := new(application.TimeProviderMock) watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } - var engine = &tick.Engine{} + var engine = &Engine{} var calls int - engine = tick.NewEngine(func(int64) { calls++; engine.Stop() }, watchMock, 1, 1, 0) + engine = NewEngine(func(int64) { calls++; engine.Stop() }, watchMock, 1, 1, 0) // Act engine.Start() diff --git a/src/node/clock/tick/watch.go b/validatornode/domain/clock/watch.go similarity index 90% rename from src/node/clock/tick/watch.go rename to validatornode/domain/clock/watch.go index 4badd8f5..c6e3334d 100644 --- a/src/node/clock/tick/watch.go +++ b/validatornode/domain/clock/watch.go @@ -1,4 +1,4 @@ -package tick +package clock import "time" diff --git a/src/encryption/private_key.go b/validatornode/domain/encryption/private_key.go similarity index 100% rename from src/encryption/private_key.go rename to validatornode/domain/encryption/private_key.go diff --git a/test/encryption/private_key_test.go b/validatornode/domain/encryption/private_key_test.go similarity index 73% rename from test/encryption/private_key_test.go rename to validatornode/domain/encryption/private_key_test.go index 344988cd..03307077 100644 --- a/test/encryption/private_key_test.go +++ b/validatornode/domain/encryption/private_key_test.go @@ -2,15 +2,15 @@ package encryption import ( "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/test" "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) func Test_NewPrivateKeyFromHex(t *testing.T) { // Arrange // Act - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + privateKey, _ := NewPrivateKeyFromHex(test.PrivateKey) // Assert expectedPrivateKey := test.PrivateKey @@ -21,7 +21,7 @@ func Test_NewPrivateKeyFromHex(t *testing.T) { func Test_NewPrivateKeyFromMnemonic(t *testing.T) { // Arrange // Act - privateKey, _ := encryption.NewPrivateKeyFromMnemonic(test.Mnemonic, test.DerivationPath, "") + privateKey, _ := NewPrivateKeyFromMnemonic(test.Mnemonic, test.DerivationPath, "") // Assert expectedPrivateKey := test.PrivateKey diff --git a/src/encryption/public_key.go b/validatornode/domain/encryption/public_key.go similarity index 100% rename from src/encryption/public_key.go rename to validatornode/domain/encryption/public_key.go diff --git a/test/encryption/public_key_test.go b/validatornode/domain/encryption/public_key_test.go similarity index 69% rename from test/encryption/public_key_test.go rename to validatornode/domain/encryption/public_key_test.go index 80921025..9707db26 100644 --- a/test/encryption/public_key_test.go +++ b/validatornode/domain/encryption/public_key_test.go @@ -2,17 +2,17 @@ package encryption import ( "fmt" - "github.com/my-cloud/ruthenium/src/encryption" - "github.com/my-cloud/ruthenium/test" "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) func Test_NewPublicKey(t *testing.T) { // Arrange - privateKey, _ := encryption.NewPrivateKeyFromHex(test.PrivateKey) + privateKey, _ := NewPrivateKeyFromHex(test.PrivateKey) // Act - publicKey := encryption.NewPublicKey(privateKey) + publicKey := NewPublicKey(privateKey) // Assert expectedPublicKey := test.PublicKey @@ -22,7 +22,7 @@ func Test_NewPublicKey(t *testing.T) { func Test_Address(t *testing.T) { // Arrange - publicKey, _ := encryption.NewPublicKeyFromHex(test.PublicKey) + publicKey, _ := NewPublicKeyFromHex(test.PublicKey) // Act address := publicKey.Address() diff --git a/src/encryption/signature.go b/validatornode/domain/encryption/signature.go similarity index 100% rename from src/encryption/signature.go rename to validatornode/domain/encryption/signature.go diff --git a/src/node/protocol/verification/block.go b/validatornode/domain/ledger/block.go similarity index 99% rename from src/node/protocol/verification/block.go rename to validatornode/domain/ledger/block.go index 33cbd91b..4ae37ad6 100644 --- a/src/node/protocol/verification/block.go +++ b/validatornode/domain/ledger/block.go @@ -1,4 +1,4 @@ -package verification +package ledger import ( "crypto/sha256" diff --git a/validatornode/domain/ledger/block_fake.go b/validatornode/domain/ledger/block_fake.go new file mode 100644 index 00000000..faa349bd --- /dev/null +++ b/validatornode/domain/ledger/block_fake.go @@ -0,0 +1,13 @@ +package ledger + +func NewGenesisBlock(validatorWalletAddress string, genesisValue uint64) *Block { + genesisTransaction, _ := NewRewardTransaction(validatorWalletAddress, true, 0, genesisValue) + transactions := []*Transaction{genesisTransaction} + return NewBlock([32]byte{}, nil, nil, 0, transactions) +} + +func NewRewardedBlock(previousHash [32]byte, timestamp int64) *Block { + rewardTransaction, _ := NewRewardTransaction("recipient", false, 0, 0) + transactions := []*Transaction{rewardTransaction} + return NewBlock(previousHash, nil, nil, timestamp, transactions) +} diff --git a/src/node/protocol/verification/input.go b/validatornode/domain/ledger/input.go similarity index 93% rename from src/node/protocol/verification/input.go rename to validatornode/domain/ledger/input.go index 8317961f..753c32d1 100644 --- a/src/node/protocol/verification/input.go +++ b/validatornode/domain/ledger/input.go @@ -1,10 +1,11 @@ -package verification +package ledger import ( "encoding/json" "errors" "fmt" - "github.com/my-cloud/ruthenium/src/encryption" + + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" ) type inputDto struct { @@ -79,3 +80,7 @@ func (input *Input) VerifySignature() error { } return nil } + +func (input *Input) Address() string { + return input.publicKey.Address() +} diff --git a/src/node/protocol/verification/input_info.go b/validatornode/domain/ledger/input_info.go similarity index 97% rename from src/node/protocol/verification/input_info.go rename to validatornode/domain/ledger/input_info.go index 386bd9a2..c4a7f05a 100644 --- a/src/node/protocol/verification/input_info.go +++ b/validatornode/domain/ledger/input_info.go @@ -1,4 +1,4 @@ -package verification +package ledger import ( "encoding/json" diff --git a/src/node/protocol/verification/output.go b/validatornode/domain/ledger/output.go similarity index 97% rename from src/node/protocol/verification/output.go rename to validatornode/domain/ledger/output.go index 51890840..423a5a36 100644 --- a/src/node/protocol/verification/output.go +++ b/validatornode/domain/ledger/output.go @@ -1,4 +1,4 @@ -package verification +package ledger import ( "encoding/json" @@ -44,10 +44,10 @@ func (output *Output) Address() string { return output.address } -func (output *Output) IsYielding() bool { - return output.isYielding -} - func (output *Output) InitialValue() uint64 { return output.value } + +func (output *Output) IsYielding() bool { + return output.isYielding +} diff --git a/src/node/protocol/verification/transaction.go b/validatornode/domain/ledger/transaction.go similarity index 72% rename from src/node/protocol/verification/transaction.go rename to validatornode/domain/ledger/transaction.go index c2e2c0d9..4cc1b4df 100644 --- a/src/node/protocol/verification/transaction.go +++ b/validatornode/domain/ledger/transaction.go @@ -1,11 +1,10 @@ -package verification +package ledger import ( "crypto/sha256" "encoding/json" "errors" "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol" ) type transactionDto struct { @@ -77,49 +76,15 @@ func (transaction *Transaction) MarshalJSON() ([]byte, error) { }) } -func (transaction *Transaction) VerifySignatures(utxoFinder protocol.UtxoFinder) error { +func (transaction *Transaction) VerifySignatures() error { for _, input := range transaction.inputs { - utxo, err := utxoFinder(input) - if err != nil { - return err - } - utxoAddress := utxo.Address() - inputAddress := input.publicKey.Address() - if utxoAddress != inputAddress { - return errors.New("output address does not derive from input public key") - } - if err = input.VerifySignature(); err != nil { + if err := input.VerifySignature(); err != nil { return fmt.Errorf("failed to verify signature of an input: %w", err) } } return nil } -func (transaction *Transaction) Fee(settings protocol.Settings, timestamp int64, utxoFinder protocol.UtxoFinder) (uint64, error) { - var inputsValue uint64 - var outputsValue uint64 - for _, input := range transaction.inputs { - utxo, err := utxoFinder(input) - if err != nil { - return 0, err - } - value := utxo.Value(timestamp, settings.HalfLifeInNanoseconds(), settings.IncomeBase(), settings.IncomeLimit()) - inputsValue += value - } - for _, output := range transaction.outputs { - outputsValue += output.InitialValue() - } - if inputsValue < outputsValue { - return 0, errors.New("transaction fee is negative") - } - fee := inputsValue - outputsValue - minimalTransactionFee := settings.MinimalTransactionFee() - if fee < minimalTransactionFee { - return 0, fmt.Errorf("transaction fee is too low, fee: %d, minimal fee: %d", fee, minimalTransactionFee) - } - return fee, nil -} - func (transaction *Transaction) Id() string { return transaction.id } diff --git a/validatornode/domain/ledger/transaction_fake.go b/validatornode/domain/ledger/transaction_fake.go new file mode 100644 index 00000000..e7eac118 --- /dev/null +++ b/validatornode/domain/ledger/transaction_fake.go @@ -0,0 +1,36 @@ +package ledger + +import ( + "encoding/json" + + "github.com/my-cloud/ruthenium/validatornode/domain/encryption" +) + +func NewSignedTransaction(inputsValue uint64, fee int, outputIndex uint16, recipientAddress string, privateKey *encryption.PrivateKey, publicKey *encryption.PublicKey, timestamp int64, transactionId string, value uint64, isYielding bool) *Transaction { + marshalledInput, _ := json.Marshal(struct { + OutputIndex uint16 `json:"output_index"` + TransactionId string `json:"transaction_id"` + }{ + OutputIndex: outputIndex, + TransactionId: transactionId, + }) + signature, _ := encryption.NewSignature(marshalledInput, privateKey) + signatureString := signature.String() + input, _ := NewInput(outputIndex, transactionId, publicKey.String(), signatureString) + sent := NewOutput(recipientAddress, false, value) + restValue := uint64(int(inputsValue) - int(value) - fee) + rest := NewOutput(recipientAddress, isYielding, restValue) + inputs := []*Input{input} + outputs := []*Output{sent, rest} + id, _ := generateId(inputs, outputs, timestamp) + dto := &transactionDto{ + Id: id, + Inputs: inputs, + Outputs: outputs, + Timestamp: timestamp, + } + marshalledTransaction, _ := json.Marshal(dto) + var transaction *Transaction + _ = json.Unmarshal(marshalledTransaction, &transaction) + return transaction +} diff --git a/src/node/protocol/validation/transaction_request.go b/validatornode/domain/ledger/transaction_request.go similarity index 70% rename from src/node/protocol/validation/transaction_request.go rename to validatornode/domain/ledger/transaction_request.go index 000cd98f..6152be1c 100644 --- a/src/node/protocol/validation/transaction_request.go +++ b/validatornode/domain/ledger/transaction_request.go @@ -1,21 +1,20 @@ -package validation +package ledger import ( "encoding/json" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" ) type transactionRequestDto struct { - Transaction *verification.Transaction + Transaction *Transaction TransactionBroadcasterTarget string } type TransactionRequest struct { - transaction *verification.Transaction + transaction *Transaction transactionBroadcasterTarget string } -func NewTransactionRequest(transaction *verification.Transaction, transactionBroadcasterTarget string) *TransactionRequest { +func NewTransactionRequest(transaction *Transaction, transactionBroadcasterTarget string) *TransactionRequest { return &TransactionRequest{transaction, transactionBroadcasterTarget} } @@ -36,7 +35,7 @@ func (request *TransactionRequest) MarshalJSON() ([]byte, error) { }) } -func (request *TransactionRequest) Transaction() *verification.Transaction { +func (request *TransactionRequest) Transaction() *Transaction { return request.transaction } diff --git a/src/node/protocol/verification/utxo.go b/validatornode/domain/ledger/utxo.go similarity index 99% rename from src/node/protocol/verification/utxo.go rename to validatornode/domain/ledger/utxo.go index 65a300b9..c98571e1 100644 --- a/src/node/protocol/verification/utxo.go +++ b/validatornode/domain/ledger/utxo.go @@ -1,4 +1,4 @@ -package verification +package ledger import ( "encoding/json" diff --git a/test/node/protocol/verification/utxo_test.go b/validatornode/domain/ledger/utxo_test.go similarity index 80% rename from test/node/protocol/verification/utxo_test.go rename to validatornode/domain/ledger/utxo_test.go index aea20ba3..6edeffb3 100644 --- a/test/node/protocol/verification/utxo_test.go +++ b/validatornode/domain/ledger/utxo_test.go @@ -1,11 +1,11 @@ -package verification +package ledger import ( "fmt" - "github.com/my-cloud/ruthenium/src/node/protocol/verification" - "github.com/my-cloud/ruthenium/test" "math" "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) const ( @@ -22,8 +22,8 @@ const ( func Test_Value_ValueIsMaxUint64AndIsYielding_ReturnsValueWithIncome(t *testing.T) { // Arrange var value uint64 = math.MaxUint64 // 18446744073709551615 - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -36,8 +36,8 @@ func Test_Value_ValueIsMaxUint64AndIsYielding_ReturnsValueWithIncome(t *testing. func Test_Value_ValueIsTwiceTheLimitAndIsYielding_ReturnsValueWithIncome(t *testing.T) { // Arrange value := 2 * limit - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -50,8 +50,8 @@ func Test_Value_ValueIsTwiceTheLimitAndIsYielding_ReturnsValueWithIncome(t *test func Test_Value_ValueIsLimitAndIsYielding_ReturnsValueWithIncome(t *testing.T) { // Arrange value := limit - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterOneDay := utxo.Value(int64(genesisTimestamp+oneDay), halfLife, base, limit) @@ -67,8 +67,8 @@ func Test_Value_ValueIsLimitAndIsYielding_ReturnsValueWithIncome(t *testing.T) { func Test_Value_ValueIs1AndIsYielding_ReturnsValueWithIncome(t *testing.T) { // Arrange var value uint64 = 1 - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterNoTimeElapsed := utxo.Value(int64(genesisTimestamp), halfLife, base, limit) @@ -83,8 +83,8 @@ func Test_Value_ValueIs1AndIsYielding_ReturnsValueWithIncome(t *testing.T) { func Test_Value_ValueIs0AndIsYielding_ReturnsValueWithIncome(t *testing.T) { // Arrange var value uint64 = 0 - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -97,8 +97,8 @@ func Test_Value_ValueIs0AndIsYielding_ReturnsValueWithIncome(t *testing.T) { func Test_Value_SamePortionOfHalfLifeElapsedAndIsYielding_ReturnsSameValue(t *testing.T) { // Arrange var value uint64 = 0 - output := verification.NewOutput("", true, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", true, value) + utxo := NewUtxo(&InputInfo{}, output, 0) portion := 1.5 // Act @@ -114,9 +114,9 @@ func Test_Value_SameElapsedTimeAndIsYielding_ReturnsSameValue(t *testing.T) { var value uint64 = 0 var outputTimestamp1 int64 = 0 var outputTimestamp2 int64 = 10 - output := verification.NewOutput("", true, value) - utxo1 := verification.NewUtxo(&verification.InputInfo{}, output, outputTimestamp1) - utxo2 := verification.NewUtxo(&verification.InputInfo{}, output, outputTimestamp2) + output := NewOutput("", true, value) + utxo1 := NewUtxo(&InputInfo{}, output, outputTimestamp1) + utxo2 := NewUtxo(&InputInfo{}, output, outputTimestamp2) var elapsedTimestamp int64 = oneDay utxo1Timestamp := outputTimestamp1 utxo2Timestamp := outputTimestamp2 @@ -133,8 +133,8 @@ func Test_Value_SameElapsedTimeAndIsYielding_ReturnsSameValue(t *testing.T) { func Test_Value_ValueIsMaxUint64AndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) { // Arrange var value uint64 = math.MaxUint64 // 18446744073709551615 - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -147,8 +147,8 @@ func Test_Value_ValueIsMaxUint64AndIsNotYielding_ReturnsValueWithoutIncome(t *te func Test_Value_ValueIsTwiceTheLimitAndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) { // Arrange value := 2 * limit - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -161,8 +161,8 @@ func Test_Value_ValueIsTwiceTheLimitAndIsNotYielding_ReturnsValueWithoutIncome(t func Test_Value_ValueIsLimitAndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) { // Arrange value := limit - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterHalfLife := utxo.Value(int64(genesisTimestamp+halfLife), halfLife, base, limit) @@ -175,8 +175,8 @@ func Test_Value_ValueIsLimitAndIsNotYielding_ReturnsValueWithoutIncome(t *testin func Test_Value_ValueIs1AndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) { // Arrange var value uint64 = 1 - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterNoTimeElapsed := utxo.Value(int64(genesisTimestamp), halfLife, base, limit) @@ -192,8 +192,8 @@ func Test_Value_ValueIs1AndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) func Test_Value_ValueIs0AndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) { // Arrange var value uint64 = 0 - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) // Act actualValueAfterOneDay := utxo.Value(int64(oneDay), halfLife, base, limit) @@ -209,8 +209,8 @@ func Test_Value_ValueIs0AndIsNotYielding_ReturnsValueWithoutIncome(t *testing.T) func Test_Value_SamePortionOfHalfLifeElapsedWithoutIncome_ReturnsSameValue(t *testing.T) { // Arrange var value uint64 = 0 - output := verification.NewOutput("", false, value) - utxo := verification.NewUtxo(&verification.InputInfo{}, output, 0) + output := NewOutput("", false, value) + utxo := NewUtxo(&InputInfo{}, output, 0) portion := 1.5 // Act @@ -226,9 +226,9 @@ func Test_Value_SameElapsedTimeWithoutIncome_ReturnsSameValue(t *testing.T) { var value uint64 = 0 var outputTimestamp1 int64 = 0 var outputTimestamp2 int64 = 10 - output := verification.NewOutput("", false, value) - utxo1 := verification.NewUtxo(&verification.InputInfo{}, output, outputTimestamp1) - utxo2 := verification.NewUtxo(&verification.InputInfo{}, output, outputTimestamp2) + output := NewOutput("", false, value) + utxo1 := NewUtxo(&InputInfo{}, output, outputTimestamp1) + utxo2 := NewUtxo(&InputInfo{}, output, outputTimestamp2) var elapsedTimestamp int64 = oneDay utxo1Timestamp := outputTimestamp1 utxo2Timestamp := outputTimestamp2 diff --git a/validatornode/infrastructure/configuration/host_settings.go b/validatornode/infrastructure/configuration/host_settings.go new file mode 100644 index 00000000..3df9da63 --- /dev/null +++ b/validatornode/infrastructure/configuration/host_settings.go @@ -0,0 +1,35 @@ +package configuration + +import ( + "encoding/json" + "strconv" +) + +type hostSettingsDto struct { + Ip string + Port int +} + +type HostSettings struct { + ip string + port string +} + +func (settings *HostSettings) UnmarshalJSON(data []byte) error { + var dto *hostSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.ip = dto.Ip + settings.port = strconv.Itoa(dto.Port) + return nil +} + +func (settings *HostSettings) Ip() string { + return settings.ip +} + +func (settings *HostSettings) Port() string { + return settings.port +} diff --git a/validatornode/infrastructure/configuration/log_settings.go b/validatornode/infrastructure/configuration/log_settings.go new file mode 100644 index 00000000..4ff7ee9e --- /dev/null +++ b/validatornode/infrastructure/configuration/log_settings.go @@ -0,0 +1,27 @@ +package configuration + +import ( + "encoding/json" +) + +type logSettingsDto struct { + Level string +} + +type LogSettings struct { + level string +} + +func (settings *LogSettings) UnmarshalJSON(data []byte) error { + var dto *logSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.level = dto.Level + return nil +} + +func (settings *LogSettings) Level() string { + return settings.level +} diff --git a/validatornode/infrastructure/configuration/network_settings.go b/validatornode/infrastructure/configuration/network_settings.go new file mode 100644 index 00000000..00c1bb00 --- /dev/null +++ b/validatornode/infrastructure/configuration/network_settings.go @@ -0,0 +1,49 @@ +package configuration + +import ( + "encoding/json" + "time" +) + +type networkSettingsDto struct { + ConnectionTimeoutInSeconds int + MaxOutboundsCount int + Seeds []string + SynchronizationIntervalInSeconds int +} + +type NetworkSettings struct { + connectionTimeout time.Duration + maxOutboundsCount int + seeds []string + synchronizationTimer time.Duration +} + +func (settings *NetworkSettings) UnmarshalJSON(data []byte) error { + var dto *networkSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.connectionTimeout = time.Duration(dto.ConnectionTimeoutInSeconds) * time.Second + settings.maxOutboundsCount = dto.MaxOutboundsCount + settings.seeds = dto.Seeds + settings.synchronizationTimer = time.Duration(dto.SynchronizationIntervalInSeconds) * time.Second + return nil +} + +func (settings *NetworkSettings) ConnectionTimeout() time.Duration { + return settings.connectionTimeout +} + +func (settings *NetworkSettings) MaxOutboundsCount() int { + return settings.maxOutboundsCount +} + +func (settings *NetworkSettings) SynchronizationTimer() time.Duration { + return settings.synchronizationTimer +} + +func (settings *NetworkSettings) Seeds() []string { + return settings.seeds +} diff --git a/validatornode/infrastructure/configuration/protocol_settings.go b/validatornode/infrastructure/configuration/protocol_settings.go new file mode 100644 index 00000000..806f0179 --- /dev/null +++ b/validatornode/infrastructure/configuration/protocol_settings.go @@ -0,0 +1,105 @@ +package configuration + +import ( + "encoding/json" + "math" + "time" +) + +type protocolSettingsDto struct { + BlocksCountLimit uint64 + CoinDigitsCount uint8 + GenesisAmount uint64 + HalfLifeInDays float64 + IncomeBase uint64 + IncomeLimit uint64 + MinimalTransactionFee uint64 + ValidationIntervalInSeconds int64 + ValidationTimeoutInSeconds int64 + VerificationsCountPerValidation int64 +} + +type ProtocolSettings struct { + bytes []byte + blocksCountLimit uint64 + genesisAmount uint64 + halfLifeInNanoseconds float64 + incomeBase uint64 + incomeLimit uint64 + minimalTransactionFee uint64 + smallestUnitsPerCoin uint64 + validationTimeout time.Duration + validationTimer time.Duration + validationTimestamp int64 + verificationsCountPerValidation int64 +} + +func (settings *ProtocolSettings) UnmarshalJSON(data []byte) error { + var dto *protocolSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.bytes = data + settings.blocksCountLimit = dto.BlocksCountLimit + settings.genesisAmount = dto.GenesisAmount + hoursByDay := 24. + settings.halfLifeInNanoseconds = dto.HalfLifeInDays * hoursByDay * float64(time.Hour.Nanoseconds()) + settings.incomeBase = dto.IncomeBase + settings.incomeLimit = dto.IncomeLimit + settings.minimalTransactionFee = dto.MinimalTransactionFee + settings.smallestUnitsPerCoin = uint64(math.Pow10(int(dto.CoinDigitsCount))) + settings.validationTimeout = time.Duration(dto.ValidationTimeoutInSeconds) * time.Second + settings.validationTimer = time.Duration(dto.ValidationIntervalInSeconds) * time.Second + settings.validationTimestamp = dto.ValidationIntervalInSeconds * time.Second.Nanoseconds() + settings.verificationsCountPerValidation = dto.VerificationsCountPerValidation + return nil +} + +func (settings *ProtocolSettings) Bytes() []byte { + return settings.bytes +} + +func (settings *ProtocolSettings) BlocksCountLimit() uint64 { + return settings.blocksCountLimit +} + +func (settings *ProtocolSettings) GenesisAmount() uint64 { + return settings.genesisAmount +} + +func (settings *ProtocolSettings) HalfLifeInNanoseconds() float64 { + return settings.halfLifeInNanoseconds +} + +func (settings *ProtocolSettings) IncomeBase() uint64 { + return settings.incomeBase +} + +func (settings *ProtocolSettings) IncomeLimit() uint64 { + return settings.incomeLimit +} + +func (settings *ProtocolSettings) MinimalTransactionFee() uint64 { + return settings.minimalTransactionFee +} + +func (settings *ProtocolSettings) SmallestUnitsPerCoin() uint64 { + return settings.smallestUnitsPerCoin +} + +func (settings *ProtocolSettings) ValidationTimeout() time.Duration { + return settings.validationTimeout +} + +func (settings *ProtocolSettings) ValidationTimer() time.Duration { + return settings.validationTimer +} + +func (settings *ProtocolSettings) ValidationTimestamp() int64 { + return settings.validationTimestamp +} + +func (settings *ProtocolSettings) VerificationsCountPerValidation() int64 { + return settings.verificationsCountPerValidation +} diff --git a/validatornode/infrastructure/configuration/registry_settings.go b/validatornode/infrastructure/configuration/registry_settings.go new file mode 100644 index 00000000..c1b84869 --- /dev/null +++ b/validatornode/infrastructure/configuration/registry_settings.go @@ -0,0 +1,28 @@ +package configuration + +import ( + "encoding/json" + "time" +) + +type registrySettingsDto struct { + SynchronizationIntervalInSeconds int +} + +type RegistrySettings struct { + synchronizationTimer time.Duration +} + +func (settings *RegistrySettings) UnmarshalJSON(data []byte) error { + var dto *registrySettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.synchronizationTimer = time.Duration(dto.SynchronizationIntervalInSeconds) * time.Second + return nil +} + +func (settings *RegistrySettings) SynchronizationTimer() time.Duration { + return settings.synchronizationTimer +} diff --git a/validatornode/infrastructure/configuration/settings.go b/validatornode/infrastructure/configuration/settings.go new file mode 100644 index 00000000..2db3b6aa --- /dev/null +++ b/validatornode/infrastructure/configuration/settings.go @@ -0,0 +1,88 @@ +package configuration + +import ( + "encoding/json" + "fmt" + "io" + "os" +) + +type settingsDto struct { + Host *HostSettings + Network *NetworkSettings + Protocol *ProtocolSettings + Registry *RegistrySettings + Validator *ValidatorSettings + Log *LogSettings +} + +type Settings struct { + host *HostSettings + network *NetworkSettings + protocol *ProtocolSettings + registry *RegistrySettings + validator *ValidatorSettings + log *LogSettings +} + +func NewSettings(path string) (*Settings, error) { + jsonFile, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open file: %w", err) + } + var settings *Settings + bytes, err := io.ReadAll(jsonFile) + if err != nil { + return nil, fmt.Errorf("unable to read file: %w", err) + } + if err = jsonFile.Close(); err != nil { + return nil, fmt.Errorf("unable to close file: %w", err) + } + if err = json.Unmarshal(bytes, &settings); err != nil { + return nil, fmt.Errorf("unable to unmarshal: %w", err) + } + return settings, nil +} + +func (settings *Settings) UnmarshalJSON(data []byte) error { + var dto *settingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.host = dto.Host + settings.network = dto.Network + settings.protocol = dto.Protocol + settings.registry = dto.Registry + settings.validator = dto.Validator + settings.log = dto.Log + return nil +} + +func (settings *Settings) Host() *HostSettings { + return settings.host +} + +func (settings *Settings) Network() *NetworkSettings { + return settings.network +} + +func (settings *Settings) Protocol() *ProtocolSettings { + return settings.protocol +} + +func (settings *Settings) Registry() *RegistrySettings { + return settings.registry +} + +func (settings *Settings) Validator() *ValidatorSettings { + return settings.validator +} + +func (settings *Settings) Log() *LogSettings { + return settings.log +} + +func (settings *Settings) ProtocolBytes() []byte { + return settings.protocol.Bytes() +} diff --git a/validatornode/infrastructure/configuration/validator_settings.go b/validatornode/infrastructure/configuration/validator_settings.go new file mode 100644 index 00000000..2f32e4fb --- /dev/null +++ b/validatornode/infrastructure/configuration/validator_settings.go @@ -0,0 +1,34 @@ +package configuration + +import ( + "encoding/json" +) + +type validatorSettingsDto struct { + Address string + InfuraKey string +} + +type ValidatorSettings struct { + address string + infuraKey string +} + +func (settings *ValidatorSettings) UnmarshalJSON(data []byte) error { + var dto *validatorSettingsDto + err := json.Unmarshal(data, &dto) + if err != nil { + return err + } + settings.address = dto.Address + settings.infuraKey = dto.InfuraKey + return nil +} + +func (settings *ValidatorSettings) Address() string { + return settings.address +} + +func (settings *ValidatorSettings) InfuraKey() string { + return settings.infuraKey +} diff --git a/src/environment/variable.go b/validatornode/infrastructure/environment/variable.go similarity index 100% rename from src/environment/variable.go rename to validatornode/infrastructure/environment/variable.go diff --git a/src/file/json_parser.go b/validatornode/infrastructure/file/json_parser.go similarity index 100% rename from src/file/json_parser.go rename to validatornode/infrastructure/file/json_parser.go diff --git a/test/file/json_parser_test.go b/validatornode/infrastructure/file/json_parser_test.go similarity index 92% rename from test/file/json_parser_test.go rename to validatornode/infrastructure/file/json_parser_test.go index 48420e25..cfcb8db7 100644 --- a/test/file/json_parser_test.go +++ b/validatornode/infrastructure/file/json_parser_test.go @@ -2,16 +2,16 @@ package file import ( "fmt" - "github.com/my-cloud/ruthenium/src/file" - "github.com/my-cloud/ruthenium/test" "os" "strings" "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) func Test_Parse_UnableToOpenFile_ReturnsError(t *testing.T) { // Arrange - parser := file.NewJsonParser() + parser := NewJsonParser() var parsed interface{} // Act @@ -33,7 +33,7 @@ func Test_Parse_UnableToUnmarshalBytes_ReturnsError(t *testing.T) { jsonData := []byte(`{`) _, _ = jsonFile.Write(jsonData) _ = jsonFile.Close() - parser := file.NewJsonParser() + parser := NewJsonParser() var person interface{} // Act @@ -62,7 +62,7 @@ func Test_Parse_ValidFile_OutputFilled(t *testing.T) { Name string `json:"name"` Age int `json:"age"` } - parser := file.NewJsonParser() + parser := NewJsonParser() var person Person // Act diff --git a/src/log/console/logger.go b/validatornode/infrastructure/log/console/logger.go similarity index 58% rename from src/log/console/logger.go rename to validatornode/infrastructure/log/console/logger.go index 2aabe59a..834329fd 100644 --- a/src/log/console/logger.go +++ b/validatornode/infrastructure/log/console/logger.go @@ -8,63 +8,67 @@ import ( type Level uint32 const ( - Debug Level = iota - Info - Warn - Error - Fatal + debug Level = iota + info + warn + err + fatal ) -func ParseLevel(level string) Level { +func parseLevel(level string) Level { switch strings.ToLower(level) { case "debug": - return Debug + return debug case "info": - return Info + return info case "warn": - return Warn + return warn case "error": - return Error + return err case "fatal": - return Fatal + return fatal } - return Info + return info } type Logger struct { level Level } -func NewLogger(level Level) *Logger { - return &Logger{level} +func NewLogger(level string) *Logger { + return &Logger{parseLevel(level)} +} + +func NewFatalLogger() *Logger { + return &Logger{fatal} } func (logger *Logger) Debug(msg string) { - if logger.level <= Debug { + if logger.level <= debug { log.Println("DEBUG:", msg) } } func (logger *Logger) Info(msg string) { - if logger.level <= Info { + if logger.level <= info { log.Println("INFO:", msg) } } func (logger *Logger) Warn(msg string) { - if logger.level <= Warn { + if logger.level <= warn { log.Println("WARN:", msg) } } func (logger *Logger) Error(msg string) { - if logger.level <= Error { + if logger.level <= err { log.Println("ERROR:", msg) } } func (logger *Logger) Fatal(msg string) { - if logger.level <= Fatal { + if logger.level <= fatal { log.Panicln("FATAL:", msg) } } diff --git a/src/log/logger.go b/validatornode/infrastructure/log/logger.go similarity index 100% rename from src/log/logger.go rename to validatornode/infrastructure/log/logger.go diff --git a/test/log/logtest/logger_mock.go b/validatornode/infrastructure/log/logger_mock.go similarity index 82% rename from test/log/logtest/logger_mock.go rename to validatornode/infrastructure/log/logger_mock.go index 79ce0a91..f949ef5c 100644 --- a/test/log/logtest/logger_mock.go +++ b/validatornode/infrastructure/log/logger_mock.go @@ -1,41 +1,40 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package logtest +package log import ( - "github.com/my-cloud/ruthenium/src/log" "sync" ) // Ensure, that LoggerMock does implement Logger. // If this is not the case, regenerate this file with moq. -var _ log.Logger = &LoggerMock{} +var _ Logger = &LoggerMock{} // LoggerMock is a mock implementation of Logger. // -// func TestSomethingThatUsesLogger(t *testing.T) { +// func TestSomethingThatUsesLogger(t *testing.T) { // -// // make and configure a mocked Logger -// mockedLogger := &LoggerMock{ -// DebugFunc: func(msg string) { -// panic("mock out the Debug method") -// }, -// ErrorFunc: func(msg string) { -// panic("mock out the Error method") -// }, -// InfoFunc: func(msg string) { -// panic("mock out the Info method") -// }, -// WarnFunc: func(msg string) { -// panic("mock out the Warn method") -// }, -// } +// // make and configure a mocked Logger +// mockedLogger := &LoggerMock{ +// DebugFunc: func(msg string) { +// panic("mock out the Debug method") +// }, +// ErrorFunc: func(msg string) { +// panic("mock out the Error method") +// }, +// InfoFunc: func(msg string) { +// panic("mock out the Info method") +// }, +// WarnFunc: func(msg string) { +// panic("mock out the Warn method") +// }, +// } // -// // use mockedLogger in code that requires Logger -// // and then make assertions. +// // use mockedLogger in code that requires Logger +// // and then make assertions. // -// } +// } type LoggerMock struct { // DebugFunc mocks the Debug method. DebugFunc func(msg string) @@ -105,7 +104,8 @@ func (mock *LoggerMock) Debug(msg string) { // DebugCalls gets all the calls that were made to Debug. // Check the length with: -// len(mockedLogger.DebugCalls()) +// +// len(mockedLogger.DebugCalls()) func (mock *LoggerMock) DebugCalls() []struct { Msg string } { @@ -136,7 +136,8 @@ func (mock *LoggerMock) Error(msg string) { // ErrorCalls gets all the calls that were made to Error. // Check the length with: -// len(mockedLogger.ErrorCalls()) +// +// len(mockedLogger.ErrorCalls()) func (mock *LoggerMock) ErrorCalls() []struct { Msg string } { @@ -167,7 +168,8 @@ func (mock *LoggerMock) Info(msg string) { // InfoCalls gets all the calls that were made to Info. // Check the length with: -// len(mockedLogger.InfoCalls()) +// +// len(mockedLogger.InfoCalls()) func (mock *LoggerMock) InfoCalls() []struct { Msg string } { @@ -198,7 +200,8 @@ func (mock *LoggerMock) Warn(msg string) { // WarnCalls gets all the calls that were made to Warn. // Check the length with: -// len(mockedLogger.WarnCalls()) +// +// len(mockedLogger.WarnCalls()) func (mock *LoggerMock) WarnCalls() []struct { Msg string } { diff --git a/validatornode/infrastructure/net/ip_finder_implementation.go b/validatornode/infrastructure/net/ip_finder_implementation.go new file mode 100644 index 00000000..8a517de8 --- /dev/null +++ b/validatornode/infrastructure/net/ip_finder_implementation.go @@ -0,0 +1,28 @@ +package net + +import ( + "fmt" + "net" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type IpFinderImplementation struct { + logger log.Logger +} + +func NewIpFinderImplementation(logger log.Logger) *IpFinderImplementation { + return &IpFinderImplementation{logger} +} + +func (finder *IpFinderImplementation) LookupIP(ip string) (string, error) { + ips, err := net.LookupIP(ip) + if err != nil { + return "", fmt.Errorf("DNS discovery failed on addresse %s: %w", ip, err) + } + ipsCount := len(ips) + if ipsCount != 1 { + return "", fmt.Errorf("DNS discovery did not find a single address (%d addresses found) for the given IP %s", ipsCount, ip) + } + return ips[0].String(), nil +} diff --git a/src/node/network/ip_finder.go b/validatornode/infrastructure/p2p/ip_finder.go similarity index 55% rename from src/node/network/ip_finder.go rename to validatornode/infrastructure/p2p/ip_finder.go index d0013f5f..f24f2df3 100644 --- a/src/node/network/ip_finder.go +++ b/validatornode/infrastructure/p2p/ip_finder.go @@ -1,6 +1,5 @@ -package network +package p2p type IpFinder interface { LookupIP(ip string) (string, error) - FindHostPublicIp() (string, error) } diff --git a/test/node/network/networktest/ip_finder_mock.go b/validatornode/infrastructure/p2p/ip_finder_mock.go similarity index 55% rename from test/node/network/networktest/ip_finder_mock.go rename to validatornode/infrastructure/p2p/ip_finder_mock.go index 4c9772f0..470b6c90 100644 --- a/test/node/network/networktest/ip_finder_mock.go +++ b/validatornode/infrastructure/p2p/ip_finder_mock.go @@ -1,16 +1,15 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package networktest +package p2p import ( - "github.com/my-cloud/ruthenium/src/node/network" "sync" ) // Ensure, that IpFinderMock does implement IpFinder. // If this is not the case, regenerate this file with moq. -var _ network.IpFinder = &IpFinderMock{} +var _ IpFinder = &IpFinderMock{} // IpFinderMock is a mock implementation of IpFinder. // @@ -18,9 +17,6 @@ var _ network.IpFinder = &IpFinderMock{} // // // make and configure a mocked IpFinder // mockedIpFinder := &IpFinderMock{ -// FindHostPublicIpFunc: func() (string, error) { -// panic("mock out the FindHostPublicIp method") -// }, // LookupIPFunc: func(ip string) (string, error) { // panic("mock out the LookupIP method") // }, @@ -31,52 +27,18 @@ var _ network.IpFinder = &IpFinderMock{} // // } type IpFinderMock struct { - // FindHostPublicIpFunc mocks the FindHostPublicIp method. - FindHostPublicIpFunc func() (string, error) - // LookupIPFunc mocks the LookupIP method. LookupIPFunc func(ip string) (string, error) // calls tracks calls to the methods. calls struct { - // FindHostPublicIp holds details about calls to the FindHostPublicIp method. - FindHostPublicIp []struct { - } // LookupIP holds details about calls to the LookupIP method. LookupIP []struct { // IP is the ip argument value. IP string } } - lockFindHostPublicIp sync.RWMutex - lockLookupIP sync.RWMutex -} - -// FindHostPublicIp calls FindHostPublicIpFunc. -func (mock *IpFinderMock) FindHostPublicIp() (string, error) { - if mock.FindHostPublicIpFunc == nil { - panic("IpFinderMock.FindHostPublicIpFunc: method is nil but IpFinder.FindHostPublicIp was just called") - } - callInfo := struct { - }{} - mock.lockFindHostPublicIp.Lock() - mock.calls.FindHostPublicIp = append(mock.calls.FindHostPublicIp, callInfo) - mock.lockFindHostPublicIp.Unlock() - return mock.FindHostPublicIpFunc() -} - -// FindHostPublicIpCalls gets all the calls that were made to FindHostPublicIp. -// Check the length with: -// -// len(mockedIpFinder.FindHostPublicIpCalls()) -func (mock *IpFinderMock) FindHostPublicIpCalls() []struct { -} { - var calls []struct { - } - mock.lockFindHostPublicIp.RLock() - calls = mock.calls.FindHostPublicIp - mock.lockFindHostPublicIp.RUnlock() - return calls + lockLookupIP sync.RWMutex } // LookupIP calls LookupIPFunc. diff --git a/validatornode/infrastructure/p2p/neighbor.go b/validatornode/infrastructure/p2p/neighbor.go new file mode 100644 index 00000000..8c68b958 --- /dev/null +++ b/validatornode/infrastructure/p2p/neighbor.go @@ -0,0 +1,100 @@ +package p2p + +import ( + "encoding/json" + "time" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/application/network" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +const ( + BlocksEndpoint = "blocks" + FirstBlockTimestampEndpoint = "first-block-timestamp" + SettingsEndpoint = "settings" + TargetsEndpoint = "targets" + TransactionEndpoint = "transaction" + TransactionsEndpoint = "transactions" + UtxosEndpoint = "utxos" +) + +type Neighbor struct { + *gp2p.Client + target *network.Target +} + +func NewNeighbor(ip string, port string, connectionTimeout time.Duration, logger log.Logger) (*Neighbor, error) { + tcp := gp2p.NewTCP(ip, port) + client, err := gp2p.NewClient(tcp) + if err != nil { + return nil, err + } + settings := gp2p.NewClientSettings() + settings.SetRetry(1, time.Nanosecond) + settings.SetConnTimeout(connectionTimeout) + client.SetSettings(settings) + client.SetLogger(logger) + target := network.NewTarget(ip, port) + return &Neighbor{client, target}, err +} + +func (neighbor *Neighbor) Target() string { + return neighbor.target.Value() +} + +func (neighbor *Neighbor) GetBlocks(startingBlockHeight uint64) ([]byte, error) { + return neighbor.sendRequest(BlocksEndpoint, startingBlockHeight) +} + +func (neighbor *Neighbor) GetFirstBlockTimestamp() (int64, error) { + res, err := neighbor.sendRequestBytes(FirstBlockTimestampEndpoint, []byte{}) + var timestamp int64 + if err != nil { + return timestamp, err + } + err = json.Unmarshal(res, ×tamp) + if err != nil { + return timestamp, err + } + return timestamp, err +} + +func (neighbor *Neighbor) GetSettings() ([]byte, error) { + return neighbor.sendRequestBytes(SettingsEndpoint, []byte{}) +} + +func (neighbor *Neighbor) SendTargets(targets []string) error { + _, err := neighbor.sendRequest(TargetsEndpoint, targets) + return err +} + +func (neighbor *Neighbor) AddTransaction(transaction []byte) error { + _, err := neighbor.sendRequestBytes(TransactionEndpoint, transaction) + return err +} + +func (neighbor *Neighbor) GetTransactions() ([]byte, error) { + return neighbor.sendRequestBytes(TransactionsEndpoint, []byte{}) +} + +func (neighbor *Neighbor) GetUtxos(address string) ([]byte, error) { + return neighbor.sendRequest(UtxosEndpoint, address) +} + +func (neighbor *Neighbor) sendRequest(topic string, request interface{}) ([]byte, error) { + bytes, err := json.Marshal(request) + if err != nil { + return nil, err + } + return neighbor.sendRequestBytes(topic, bytes) +} + +func (neighbor *Neighbor) sendRequestBytes(topic string, request []byte) ([]byte, error) { + data, err := neighbor.Client.Send(topic, gp2p.Data{Bytes: request}) + if err != nil { + return []byte{}, err + } + return data.Bytes, err +} diff --git a/validatornode/infrastructure/p2p/neighbor_factory.go b/validatornode/infrastructure/p2p/neighbor_factory.go new file mode 100644 index 00000000..ee5cc270 --- /dev/null +++ b/validatornode/infrastructure/p2p/neighbor_factory.go @@ -0,0 +1,31 @@ +package p2p + +import ( + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application" + "time" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" +) + +type NeighborFactory struct { + ipFinder IpFinder + connectionTimeout time.Duration + logger log.Logger +} + +func NewNeighborFactory(ipFinder IpFinder, connectionTimeout time.Duration, logger log.Logger) *NeighborFactory { + return &NeighborFactory{ipFinder, connectionTimeout, logger} +} + +func (factory *NeighborFactory) CreateSender(ip string, port string) (application.Sender, error) { + lookedUpIp, err := factory.ipFinder.LookupIP(ip) + if err != nil { + return nil, fmt.Errorf("failed to look up IP on addresse %s: %w", ip, err) + } + neighbor, err := NewNeighbor(lookedUpIp, port, factory.connectionTimeout, factory.logger) + if err != nil { + return nil, fmt.Errorf("failed to create neighbor for address %s: %w", ip, err) + } + return neighbor, err +} diff --git a/validatornode/infrastructure/p2p/neighbor_factory_test.go b/validatornode/infrastructure/p2p/neighbor_factory_test.go new file mode 100644 index 00000000..4bebb78f --- /dev/null +++ b/validatornode/infrastructure/p2p/neighbor_factory_test.go @@ -0,0 +1,37 @@ +package p2p + +import ( + "errors" + "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_CreateSender_IpFinderError_ReturnsNil(t *testing.T) { + // Arrange + ipFinder := new(IpFinderMock) + ipFinder.LookupIPFunc = func(string) (string, error) { return "", errors.New("") } + logger := log.NewLoggerMock() + neighborFactory := NewNeighborFactory(ipFinder, 0, logger) + + // Act + client, _ := neighborFactory.CreateSender("", "0") + + // Assert + test.Assert(t, client == nil, "client is not nil whereas it should be") +} + +func Test_CreateSender_ValidIp_ReturnsClient(t *testing.T) { + // Arrange + ipFinder := new(IpFinderMock) + ipFinder.LookupIPFunc = func(string) (string, error) { return "", nil } + logger := log.NewLoggerMock() + neighborFactory := NewNeighborFactory(ipFinder, 0, logger) + + // Act + client, _ := neighborFactory.CreateSender("", "0") + + // Assert + test.Assert(t, client != nil, "client is nil whereas it should not") +} diff --git a/src/node/protocol/poh/registry.go b/validatornode/infrastructure/poh/humanity_registry.go similarity index 59% rename from src/node/protocol/poh/registry.go rename to validatornode/infrastructure/poh/humanity_registry.go index c063cb11..16f7a3dd 100644 --- a/src/node/protocol/poh/registry.go +++ b/validatornode/infrastructure/poh/humanity_registry.go @@ -2,9 +2,11 @@ package poh import ( "fmt" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" - "github.com/my-cloud/ruthenium/src/log" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log" ) const ( @@ -12,18 +14,18 @@ const ( clientUrl = "https://mainnet.infura.io/v3/" ) -type Registry struct { +type HumanityRegistry struct { infuraKey string } -func NewRegistry(infuraKey string, logger log.Logger) *Registry { +func NewHumanityRegistry(infuraKey string, logger log.Logger) *HumanityRegistry { if infuraKey == "" { logger.Warn("infura key not provided") } - return &Registry{infuraKey} + return &HumanityRegistry{infuraKey} } -func (registry *Registry) IsRegistered(address string) (isRegistered bool, err error) { +func (registry *HumanityRegistry) IsRegistered(address string) (isRegistered bool, err error) { if registry.infuraKey == "" { return true, nil } @@ -34,11 +36,11 @@ func (registry *Registry) IsRegistered(address string) (isRegistered bool, err e pohSmartContractAddress := common.HexToAddress(pohSmartContractAddressHex) proofOfHumanity, err := NewPoh(pohSmartContractAddress, client) if err != nil { - return + return false, fmt.Errorf("failed to get proof of humanity for address %s: %w", address, err) } isRegistered, err = proofOfHumanity.PohCaller.IsRegistered(nil, common.HexToAddress(address)) if err != nil { - return + return false, fmt.Errorf("failed to get proof of humanity for address %s: %w", address, err) } - return + return isRegistered, nil } diff --git a/src/node/protocol/poh/proof_of_humanity.go b/validatornode/infrastructure/poh/proof_of_humanity.go similarity index 99% rename from src/node/protocol/poh/proof_of_humanity.go rename to validatornode/infrastructure/poh/proof_of_humanity.go index cd7becf5..53826cd6 100644 --- a/src/node/protocol/poh/proof_of_humanity.go +++ b/validatornode/infrastructure/poh/proof_of_humanity.go @@ -8,7 +8,7 @@ import ( "math/big" "strings" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" diff --git a/test/assertion.go b/validatornode/infrastructure/test/assertion.go similarity index 88% rename from test/assertion.go rename to validatornode/infrastructure/test/assertion.go index e0f8ee12..0a3f2eec 100644 --- a/test/assertion.go +++ b/validatornode/infrastructure/test/assertion.go @@ -16,7 +16,7 @@ func Assert(t testing.TB, condition bool, msg string, v ...interface{}) { } } -func AssertThatMessageIsLogged(t testing.TB, expectedMessages []string, loggedMessageStructs []struct{ Msg string }) { +func AssertThatMessageIsLogged(t testing.TB, loggedMessageStructs []struct{ Msg string }, expectedMessages ...string) { isLoggedByExpectedMessage := make(map[string]bool) var loggedMessages []string for _, call := range loggedMessageStructs { diff --git a/test/dataset.go b/validatornode/infrastructure/test/dataset.go similarity index 100% rename from test/dataset.go rename to validatornode/infrastructure/test/dataset.go diff --git a/validatornode/main.go b/validatornode/main.go new file mode 100644 index 00000000..2b079bec --- /dev/null +++ b/validatornode/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "flag" + "fmt" + "github.com/my-cloud/ruthenium/validatornode/application/validation" + "github.com/my-cloud/ruthenium/validatornode/application/verification" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/net" + "github.com/my-cloud/ruthenium/validatornode/presentation/api" + "io" + "net/http" + + "github.com/my-cloud/ruthenium/validatornode/application/network" + "github.com/my-cloud/ruthenium/validatornode/domain/clock" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/configuration" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/environment" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log/console" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/p2p" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/poh" + "github.com/my-cloud/ruthenium/validatornode/presentation" +) + +func main() { + settingsPath := flag.String("settings-path", environment.NewVariable("SETTINGS_PATH").GetStringValue("validatornode/settings.json"), "The settings file path") + flag.Parse() + settings, err := configuration.NewSettings(*settingsPath) + if err != nil { + panic(err.Error()) + } + logger := console.NewLogger(settings.Log().Level()) + node, err := createHostNode(settings, logger) + if err != nil { + logger.Fatal(err.Error()) + } else if err = node.Run(); err != nil { + logger.Fatal(fmt.Errorf("failed to run host validator node: %w", err).Error()) + } +} + +func createHostNode(settings *configuration.Settings, logger *console.Logger) (*presentation.Node, error) { + humanityRegistry := poh.NewHumanityRegistry(settings.Validator().InfuraKey(), logger) + addressesRegistry := verification.NewAddressesRegistry(humanityRegistry, logger) + watch := clock.NewWatch() + seedsStringTargets := settings.Network().Seeds() + scoresBySeedTargetValue := map[string]int{} + for _, seedStringTargetValue := range seedsStringTargets { + scoresBySeedTargetValue[seedStringTargetValue] = 0 + } + ipFinder := net.NewIpFinderImplementation(logger) + neighborFactory := p2p.NewNeighborFactory(ipFinder, settings.Network().ConnectionTimeout(), console.NewFatalLogger()) + hostIp, err := findHostPublicIp(settings.Host().Ip(), logger) + if err != nil { + return nil, err + } + neighborhood := network.NewNeighborhood(neighborFactory, hostIp, settings.Host().Port(), settings.Network().MaxOutboundsCount(), scoresBySeedTargetValue, watch) + utxosRegistry := verification.NewUtxosRegistry(settings.Protocol()) + blockchain := verification.NewBlockchain(addressesRegistry, settings.Protocol(), neighborhood, utxosRegistry, logger) + transactionsPool := validation.NewTransactionsPool(blockchain, settings.Protocol(), neighborhood, utxosRegistry, settings.Validator().Address(), logger) + neighborhoodSynchronizationEngine := clock.NewEngine(neighborhood.Synchronize, watch, settings.Network().SynchronizationTimer(), 1, 0) + validationEngine := clock.NewEngine(transactionsPool.Validate, watch, settings.Protocol().ValidationTimer(), 1, 0) + verificationEngine := clock.NewEngine(blockchain.Update, watch, settings.Protocol().ValidationTimer(), settings.Protocol().VerificationsCountPerValidation(), 1) + registrySynchronizationEngine := clock.NewEngine(addressesRegistry.Synchronize, watch, settings.Registry().SynchronizationTimer(), 1, 0) + host, err := api.NewHost(blockchain, neighborhood, transactionsPool, utxosRegistry, settings.Host().Port(), settings.ProtocolBytes(), settings.Protocol().ValidationTimeout()) + if err != nil { + return nil, err + } + logger.Info(fmt.Sprintf("host validator node running for address: %s", settings.Validator().Address())) + return presentation.NewNode(host, neighborhoodSynchronizationEngine, validationEngine, verificationEngine, registrySynchronizationEngine), nil +} + +func findHostPublicIp(ip string, logger *console.Logger) (string, error) { + if ip != "" { + return ip, nil + } + resp, err := http.Get("https://ifconfig.me") + if err != nil { + return "", fmt.Errorf("failed to find the public IP: %w", err) + } + defer func() { + if bodyCloseError := resp.Body.Close(); bodyCloseError != nil { + logger.Error(fmt.Errorf("failed to close public IP request body: %w", bodyCloseError).Error()) + } + }() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} diff --git a/validatornode/presentation/api/history/blocks_controller.go b/validatornode/presentation/api/history/blocks_controller.go new file mode 100644 index 00000000..1db2fd55 --- /dev/null +++ b/validatornode/presentation/api/history/blocks_controller.go @@ -0,0 +1,44 @@ +package history + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + + gp2p "github.com/leprosus/golang-p2p" +) + +type BlocksController struct { + blocksManager application.BlocksManager +} + +func NewBlocksController(blocksManager application.BlocksManager) *BlocksController { + return &BlocksController{blocksManager} +} + +func (controller *BlocksController) HandleBlocksRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { + var startingBlockHeight uint64 + res := gp2p.Data{} + data := req.GetBytes() + if err := json.Unmarshal(data, &startingBlockHeight); err != nil { + return res, err + } + blocks := controller.blocksManager.Blocks(startingBlockHeight) + blocksBytes, err := json.Marshal(blocks) + if err != nil { + return res, err + } + res.SetBytes(blocksBytes) + return res, nil +} + +func (controller *BlocksController) HandleFirstBlockTimestampRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { + res := gp2p.Data{} + timestamp := controller.blocksManager.FirstBlockTimestamp() + timestampBytes, err := json.Marshal(timestamp) + if err != nil { + return res, err + } + res.SetBytes(timestampBytes) + return res, nil +} diff --git a/validatornode/presentation/api/history/blocks_controller_test.go b/validatornode/presentation/api/history/blocks_controller_test.go new file mode 100644 index 00000000..512535e0 --- /dev/null +++ b/validatornode/presentation/api/history/blocks_controller_test.go @@ -0,0 +1,45 @@ +package history + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + "testing" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_HandleFirstBlockTimestampRequest_ValidRequest_FirstBlockTimestampCalled(t *testing.T) { + // Arrange + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.FirstBlockTimestampFunc = func() int64 { return 0 } + controller := NewBlocksController(blocksManagerMock) + req := gp2p.Data{} + + // Act + _, _ = controller.HandleFirstBlockTimestampRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(blocksManagerMock.FirstBlockTimestampCalls()) != 0 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} + +func Test_HandleBlocksRequest_ValidBlocksRequest_LastBlocksCalled(t *testing.T) { + // Arrange + blocksManagerMock := new(application.BlocksManagerMock) + blocksManagerMock.BlocksFunc = func(uint64) []*ledger.Block { return nil } + controller := NewBlocksController(blocksManagerMock) + var height uint64 = 0 + marshalledHeight, _ := json.Marshal(&height) + req := gp2p.Data{Bytes: marshalledHeight} + + // Act + _, _ = controller.HandleBlocksRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(blocksManagerMock.BlocksCalls()) == 1 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} diff --git a/validatornode/presentation/api/host.go b/validatornode/presentation/api/host.go new file mode 100644 index 00000000..3087c1c3 --- /dev/null +++ b/validatornode/presentation/api/host.go @@ -0,0 +1,77 @@ +package api + +import ( + "fmt" + gp2p "github.com/leprosus/golang-p2p" + "github.com/my-cloud/ruthenium/validatornode/application" + "github.com/my-cloud/ruthenium/validatornode/presentation/api/history" + "github.com/my-cloud/ruthenium/validatornode/presentation/api/network" + "github.com/my-cloud/ruthenium/validatornode/presentation/api/payment" + "github.com/my-cloud/ruthenium/validatornode/presentation/api/protocol" + "github.com/my-cloud/ruthenium/validatornode/presentation/api/wallet" + "time" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/log/console" +) + +type Host struct { + *gp2p.Server + blocksController *history.BlocksController + sendersController *network.SendersController + settingsController *protocol.SettingsController + transactionsController *payment.TransactionsController + utxosController *wallet.UtxosController +} + +func NewHost(blocksManager application.BlocksManager, + sendersManager application.SendersManager, + transactionsManager application.TransactionsManager, + utxosManager application.UtxosManager, + hostPort string, + protocolSettingsBytes []byte, + validationTimeout time.Duration) (*Host, error) { + port := hostPort + tcp := gp2p.NewTCP("0.0.0.0", hostPort) + server, err := gp2p.NewServer(tcp) + if err != nil { + return nil, fmt.Errorf("failed to instantiate host on port %s: %w", port, err) + } + server.SetLogger(console.NewFatalLogger()) + serverSettings := gp2p.NewServerSettings() + serverSettings.SetConnTimeout(validationTimeout) + server.SetSettings(serverSettings) + blocksController := history.NewBlocksController(blocksManager) + sendersController := network.NewSendersController(sendersManager) + settingsController := protocol.NewSettingsController(protocolSettingsBytes) + transactionsController := payment.NewTransactionsController(sendersManager, transactionsManager) + utxosController := wallet.NewUtxosController(utxosManager) + return &Host{server, blocksController, sendersController, settingsController, transactionsController, utxosController}, err +} + +func (host *Host) SetHandleBlocksRequest(endpoint string) { + host.SetHandle(endpoint, host.blocksController.HandleBlocksRequest) +} + +func (host *Host) SetHandleFirstBlockTimestampRequest(endpoint string) { + host.SetHandle(endpoint, host.blocksController.HandleFirstBlockTimestampRequest) +} + +func (host *Host) SetHandleSettingsRequest(endpoint string) { + host.SetHandle(endpoint, host.settingsController.HandleSettingsRequest) +} + +func (host *Host) SetHandleTargetsRequest(endpoint string) { + host.SetHandle(endpoint, host.sendersController.HandleTargetsRequest) +} + +func (host *Host) SetHandleTransactionRequest(endpoint string) { + host.SetHandle(endpoint, host.transactionsController.HandleTransactionRequest) +} + +func (host *Host) SetHandleTransactionsRequest(endpoint string) { + host.SetHandle(endpoint, host.transactionsController.HandleTransactionsRequest) +} + +func (host *Host) SetHandleUtxosRequest(endpoint string) { + host.SetHandle(endpoint, host.utxosController.HandleUtxosRequest) +} diff --git a/validatornode/presentation/api/network/senders_controller.go b/validatornode/presentation/api/network/senders_controller.go new file mode 100644 index 00000000..a8ef7b96 --- /dev/null +++ b/validatornode/presentation/api/network/senders_controller.go @@ -0,0 +1,28 @@ +package network + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + + gp2p "github.com/leprosus/golang-p2p" +) + +type SendersController struct { + sendersManager application.SendersManager +} + +func NewSendersController(sendersManager application.SendersManager) *SendersController { + return &SendersController{sendersManager} +} + +func (controller *SendersController) HandleTargetsRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { + res := gp2p.Data{} + var targets []string + data := req.GetBytes() + if err := json.Unmarshal(data, &targets); err != nil { + return res, err + } + go controller.sendersManager.AddTargets(targets) + return res, nil +} diff --git a/validatornode/presentation/api/network/senders_controller_test.go b/validatornode/presentation/api/network/senders_controller_test.go new file mode 100644 index 00000000..fcaa418b --- /dev/null +++ b/validatornode/presentation/api/network/senders_controller_test.go @@ -0,0 +1,50 @@ +package network + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + "sync" + "testing" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_HandleTargetsRequest_AddInvalidTargets_AddTargetsNotCalled(t *testing.T) { + // Arrange + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.AddTargetsFunc = func([]string) {} + controller := NewSendersController(sendersManagerMock) + targets := []string{"target"} + marshalledTargets, _ := json.Marshal(targets) + req := gp2p.Data{Bytes: marshalledTargets} + + // Act + _, _ = controller.HandleTargetsRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(sendersManagerMock.AddTargetsCalls()) != 0 + test.Assert(t, !isMethodCalled, "Method is called whereas it should not.") +} + +func Test_HandleTargetsRequest_AddValidTargets_AddTargetsCalled(t *testing.T) { + // Arrange + waitGroup := sync.WaitGroup{} + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.AddTargetsFunc = func([]string) { waitGroup.Done() } + controller := NewSendersController(sendersManagerMock) + targets := []string{"target"} + marshalledTargets, _ := json.Marshal(targets) + req := gp2p.Data{Bytes: marshalledTargets} + waitGroup.Add(1) + + // Act + _, _ = controller.HandleTargetsRequest(context.TODO(), req) + + // Assert + waitGroup.Wait() + isMethodCalled := len(sendersManagerMock.AddTargetsCalls()) == 1 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} diff --git a/validatornode/presentation/api/payment/transactions_controller.go b/validatornode/presentation/api/payment/transactions_controller.go new file mode 100644 index 00000000..8bd2f0ff --- /dev/null +++ b/validatornode/presentation/api/payment/transactions_controller.go @@ -0,0 +1,42 @@ +package payment + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" +) + +type TransactionsController struct { + sendersManager application.SendersManager + transactionsManager application.TransactionsManager +} + +func NewTransactionsController(sendersManager application.SendersManager, transactionsManager application.TransactionsManager) *TransactionsController { + return &TransactionsController{sendersManager, transactionsManager} +} + +func (controller *TransactionsController) HandleTransactionRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { + var transactionRequest *ledger.TransactionRequest + data := req.GetBytes() + res := gp2p.Data{} + if err := json.Unmarshal(data, &transactionRequest); err != nil { + return res, err + } + go controller.transactionsManager.AddTransaction(transactionRequest.Transaction(), transactionRequest.TransactionBroadcasterTarget(), controller.sendersManager.HostTarget()) + return res, nil +} + +func (controller *TransactionsController) HandleTransactionsRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { + res := gp2p.Data{} + transactions := controller.transactionsManager.Transactions() + transactionsBytes, err := json.Marshal(transactions) + if err != nil { + return res, err + } + res.SetBytes(transactionsBytes) + return res, nil +} diff --git a/validatornode/presentation/api/payment/transactions_controller_test.go b/validatornode/presentation/api/payment/transactions_controller_test.go new file mode 100644 index 00000000..906123d2 --- /dev/null +++ b/validatornode/presentation/api/payment/transactions_controller_test.go @@ -0,0 +1,69 @@ +package payment + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + "sync" + "testing" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_HandleTransactionRequest_AddValidTransaction_AddTransactionCalled(t *testing.T) { + // Arrange + waitGroup := sync.WaitGroup{} + transactionsManagerMock := new(application.TransactionsManagerMock) + transactionsManagerMock.AddTransactionFunc = func(*ledger.Transaction, string, string) { waitGroup.Done() } + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.HostTargetFunc = func() string { return "" } + controller := NewTransactionsController(sendersManagerMock, transactionsManagerMock) + req := gp2p.Data{} + transaction, _ := ledger.NewRewardTransaction("", false, 0, 0) + transactionBytes, _ := json.Marshal(transaction) + req.SetBytes(transactionBytes) + waitGroup.Add(1) + + // Act + _, _ = controller.HandleTransactionRequest(context.TODO(), req) + + // Assert + waitGroup.Wait() + isMethodCalled := len(transactionsManagerMock.AddTransactionCalls()) == 1 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} + +func Test_HandleTransactionRequest_AddInvalidValidTransaction_AddTransactionNotCalled(t *testing.T) { + // Arrange + transactionsManagerMock := new(application.TransactionsManagerMock) + sendersManagerMock := new(application.SendersManagerMock) + sendersManagerMock.HostTargetFunc = func() string { return "" } + controller := NewTransactionsController(sendersManagerMock, transactionsManagerMock) + req := gp2p.Data{} + + // Act + _, err := controller.HandleTransactionRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(transactionsManagerMock.AddTransactionCalls()) == 0 + test.Assert(t, err != nil, "Error is nil whereas it should not.") + test.Assert(t, isMethodCalled, "Method is called whereas it should not.") +} + +func Test_HandleTransactionsRequest_ValidTransactionsRequest_TransactionsCalled(t *testing.T) { + // Arrange + transactionsManagerMock := new(application.TransactionsManagerMock) + transactionsManagerMock.TransactionsFunc = func() []*ledger.Transaction { return nil } + controller := NewTransactionsController(nil, transactionsManagerMock) + req := gp2p.Data{} + + // Act + _, _ = controller.HandleTransactionsRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(transactionsManagerMock.TransactionsCalls()) == 1 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} diff --git a/validatornode/presentation/api/protocol/settings_controller.go b/validatornode/presentation/api/protocol/settings_controller.go new file mode 100644 index 00000000..2850fcb0 --- /dev/null +++ b/validatornode/presentation/api/protocol/settings_controller.go @@ -0,0 +1,21 @@ +package protocol + +import ( + "context" + + gp2p "github.com/leprosus/golang-p2p" +) + +type SettingsController struct { + settings []byte +} + +func NewSettingsController(settings []byte) *SettingsController { + return &SettingsController{settings} +} + +func (controller *SettingsController) HandleSettingsRequest(_ context.Context, _ gp2p.Data) (gp2p.Data, error) { + res := gp2p.Data{} + res.SetBytes(controller.settings) + return res, nil +} diff --git a/validatornode/presentation/api/protocol/settings_controller_test.go b/validatornode/presentation/api/protocol/settings_controller_test.go new file mode 100644 index 00000000..6017e1d1 --- /dev/null +++ b/validatornode/presentation/api/protocol/settings_controller_test.go @@ -0,0 +1,25 @@ +package protocol + +import ( + "context" + "reflect" + "testing" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_HandleSettingsRequest_ValidRequest_SettingsCalled(t *testing.T) { + // Arrange + expectedSettings := []byte{0} + controller := NewSettingsController(expectedSettings) + req := gp2p.Data{} + + // Act + data, _ := controller.HandleSettingsRequest(context.TODO(), req) + + // Assert + actualSettings := data.GetBytes() + test.Assert(t, reflect.DeepEqual(expectedSettings, actualSettings), "Settings are not the expected ones.") +} diff --git a/validatornode/presentation/api/wallet/utxos_controller.go b/validatornode/presentation/api/wallet/utxos_controller.go new file mode 100644 index 00000000..d79a0cec --- /dev/null +++ b/validatornode/presentation/api/wallet/utxos_controller.go @@ -0,0 +1,33 @@ +package wallet + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + + gp2p "github.com/leprosus/golang-p2p" +) + +type UtxosController struct { + utxosManager application.UtxosManager +} + +func NewUtxosController(utxosManager application.UtxosManager) *UtxosController { + return &UtxosController{utxosManager} +} + +func (controller *UtxosController) HandleUtxosRequest(_ context.Context, req gp2p.Data) (gp2p.Data, error) { + res := gp2p.Data{} + var address string + data := req.GetBytes() + if err := json.Unmarshal(data, &address); err != nil { + return res, err + } + utxosByAddress := controller.utxosManager.Utxos(address) + utxosByAddressBytes, err := json.Marshal(utxosByAddress) + if err != nil { + return res, err + } + res.SetBytes(utxosByAddressBytes) + return res, nil +} diff --git a/validatornode/presentation/api/wallet/utxos_controller_test.go b/validatornode/presentation/api/wallet/utxos_controller_test.go new file mode 100644 index 00000000..0da164ab --- /dev/null +++ b/validatornode/presentation/api/wallet/utxos_controller_test.go @@ -0,0 +1,30 @@ +package wallet + +import ( + "context" + "encoding/json" + "github.com/my-cloud/ruthenium/validatornode/application" + "testing" + + gp2p "github.com/leprosus/golang-p2p" + + "github.com/my-cloud/ruthenium/validatornode/domain/ledger" + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" +) + +func Test_HandleUtxosRequest_ValidUtxosRequest_UtxosByAddressCalled(t *testing.T) { + // Arrange + utxosManagerMock := new(application.UtxosManagerMock) + utxosManagerMock.UtxosFunc = func(string) []*ledger.Utxo { return nil } + controller := NewUtxosController(utxosManagerMock) + address := "address" + marshalledAddress, _ := json.Marshal(&address) + req := gp2p.Data{Bytes: marshalledAddress} + + // Act + _, _ = controller.HandleUtxosRequest(context.TODO(), req) + + // Assert + isMethodCalled := len(utxosManagerMock.UtxosCalls()) == 1 + test.Assert(t, isMethodCalled, "Method is not called whereas it should be.") +} diff --git a/validatornode/presentation/node.go b/validatornode/presentation/node.go new file mode 100644 index 00000000..03753be8 --- /dev/null +++ b/validatornode/presentation/node.go @@ -0,0 +1,26 @@ +package presentation + +import "github.com/my-cloud/ruthenium/validatornode/infrastructure/p2p" + +type Node struct { + server Server + engines []Pulser +} + +func NewNode(server Server, engines ...Pulser) *Node { + server.SetHandleBlocksRequest(p2p.BlocksEndpoint) + server.SetHandleFirstBlockTimestampRequest(p2p.FirstBlockTimestampEndpoint) + server.SetHandleSettingsRequest(p2p.SettingsEndpoint) + server.SetHandleTargetsRequest(p2p.TargetsEndpoint) + server.SetHandleTransactionRequest(p2p.TransactionEndpoint) + server.SetHandleTransactionsRequest(p2p.TransactionsEndpoint) + server.SetHandleUtxosRequest(p2p.UtxosEndpoint) + return &Node{server, engines} +} + +func (node *Node) Run() error { + for _, engine := range node.engines { + go engine.Start() + } + return node.server.Serve() +} diff --git a/test/node/network/p2p/host_test.go b/validatornode/presentation/node_test.go similarity index 57% rename from test/node/network/p2p/host_test.go rename to validatornode/presentation/node_test.go index 7c7a51af..89747a66 100644 --- a/test/node/network/p2p/host_test.go +++ b/validatornode/presentation/node_test.go @@ -1,17 +1,14 @@ -package p2p +package presentation import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" - "github.com/my-cloud/ruthenium/test" - "github.com/my-cloud/ruthenium/test/log/logtest" - "github.com/my-cloud/ruthenium/test/node/clock/clocktest" - "github.com/my-cloud/ruthenium/test/node/network/p2p/p2ptest" "testing" + + "github.com/my-cloud/ruthenium/validatornode/infrastructure/test" ) func Test_Run_NoError_ServerStarted(t *testing.T) { // Arrange - serverMock := new(p2ptest.ServerMock) + serverMock := new(ServerMock) serverMock.ServeFunc = func() error { return nil } serverMock.SetHandleBlocksRequestFunc = func(string) {} serverMock.SetHandleFirstBlockTimestampRequestFunc = func(string) {} @@ -20,15 +17,13 @@ func Test_Run_NoError_ServerStarted(t *testing.T) { serverMock.SetHandleTransactionRequestFunc = func(string) {} serverMock.SetHandleTransactionsRequestFunc = func(string) {} serverMock.SetHandleUtxosRequestFunc = func(string) {} - engineMock := new(clocktest.EngineMock) + engineMock := new(PulserMock) engineMock.StartFunc = func() {} - engineMock.DoFunc = func() {} - engineMock.WaitFunc = func() {} - logger := logtest.NewLoggerMock() - host := p2p.NewHost(serverMock, engineMock, engineMock, engineMock, logger) + engineMock.PulseFunc = func() {} + node := NewNode(serverMock, engineMock) // Act - _ = host.Run() + _ = node.Run() // Assert isServerStarted := len(serverMock.ServeCalls()) == 1 diff --git a/validatornode/presentation/pulser.go b/validatornode/presentation/pulser.go new file mode 100644 index 00000000..c85b90d2 --- /dev/null +++ b/validatornode/presentation/pulser.go @@ -0,0 +1,7 @@ +package presentation + +type Pulser interface { + Start() + Stop() + Pulse() +} diff --git a/validatornode/presentation/pulser_mock.go b/validatornode/presentation/pulser_mock.go new file mode 100644 index 00000000..6c0e49c3 --- /dev/null +++ b/validatornode/presentation/pulser_mock.go @@ -0,0 +1,141 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package presentation + +import ( + "sync" +) + +// Ensure, that PulserMock does implement Pulser. +// If this is not the case, regenerate this file with moq. +var _ Pulser = &PulserMock{} + +// PulserMock is a mock implementation of Pulser. +// +// func TestSomethingThatUsesPulser(t *testing.T) { +// +// // make and configure a mocked Pulser +// mockedPulser := &PulserMock{ +// PulseFunc: func() { +// panic("mock out the Pulse method") +// }, +// StartFunc: func() { +// panic("mock out the Start method") +// }, +// StopFunc: func() { +// panic("mock out the Stop method") +// }, +// } +// +// // use mockedPulser in code that requires Pulser +// // and then make assertions. +// +// } +type PulserMock struct { + // PulseFunc mocks the Pulse method. + PulseFunc func() + + // StartFunc mocks the Start method. + StartFunc func() + + // StopFunc mocks the Stop method. + StopFunc func() + + // calls tracks calls to the methods. + calls struct { + // Pulse holds details about calls to the Pulse method. + Pulse []struct { + } + // Start holds details about calls to the Start method. + Start []struct { + } + // Stop holds details about calls to the Stop method. + Stop []struct { + } + } + lockPulse sync.RWMutex + lockStart sync.RWMutex + lockStop sync.RWMutex +} + +// Pulse calls PulseFunc. +func (mock *PulserMock) Pulse() { + if mock.PulseFunc == nil { + panic("PulserMock.PulseFunc: method is nil but Pulser.Pulse was just called") + } + callInfo := struct { + }{} + mock.lockPulse.Lock() + mock.calls.Pulse = append(mock.calls.Pulse, callInfo) + mock.lockPulse.Unlock() + mock.PulseFunc() +} + +// PulseCalls gets all the calls that were made to Pulse. +// Check the length with: +// +// len(mockedPulser.PulseCalls()) +func (mock *PulserMock) PulseCalls() []struct { +} { + var calls []struct { + } + mock.lockPulse.RLock() + calls = mock.calls.Pulse + mock.lockPulse.RUnlock() + return calls +} + +// Start calls StartFunc. +func (mock *PulserMock) Start() { + if mock.StartFunc == nil { + panic("PulserMock.StartFunc: method is nil but Pulser.Start was just called") + } + callInfo := struct { + }{} + mock.lockStart.Lock() + mock.calls.Start = append(mock.calls.Start, callInfo) + mock.lockStart.Unlock() + mock.StartFunc() +} + +// StartCalls gets all the calls that were made to Start. +// Check the length with: +// +// len(mockedPulser.StartCalls()) +func (mock *PulserMock) StartCalls() []struct { +} { + var calls []struct { + } + mock.lockStart.RLock() + calls = mock.calls.Start + mock.lockStart.RUnlock() + return calls +} + +// Stop calls StopFunc. +func (mock *PulserMock) Stop() { + if mock.StopFunc == nil { + panic("PulserMock.StopFunc: method is nil but Pulser.Stop was just called") + } + callInfo := struct { + }{} + mock.lockStop.Lock() + mock.calls.Stop = append(mock.calls.Stop, callInfo) + mock.lockStop.Unlock() + mock.StopFunc() +} + +// StopCalls gets all the calls that were made to Stop. +// Check the length with: +// +// len(mockedPulser.StopCalls()) +func (mock *PulserMock) StopCalls() []struct { +} { + var calls []struct { + } + mock.lockStop.RLock() + calls = mock.calls.Stop + mock.lockStop.RUnlock() + return calls +} diff --git a/src/node/network/p2p/server.go b/validatornode/presentation/server.go similarity index 94% rename from src/node/network/p2p/server.go rename to validatornode/presentation/server.go index c8823bc6..c6523d6d 100644 --- a/src/node/network/p2p/server.go +++ b/validatornode/presentation/server.go @@ -1,4 +1,4 @@ -package p2p +package presentation type Server interface { Serve() (err error) diff --git a/test/node/network/p2p/p2ptest/server_mock.go b/validatornode/presentation/server_mock.go similarity index 98% rename from test/node/network/p2p/p2ptest/server_mock.go rename to validatornode/presentation/server_mock.go index 28de1431..e4fa26b2 100644 --- a/test/node/network/p2p/p2ptest/server_mock.go +++ b/validatornode/presentation/server_mock.go @@ -1,16 +1,15 @@ // Code generated by moq; DO NOT EDIT. // github.com/matryer/moq -package p2ptest +package presentation import ( - "github.com/my-cloud/ruthenium/src/node/network/p2p" "sync" ) // Ensure, that ServerMock does implement Server. // If this is not the case, regenerate this file with moq. -var _ p2p.Server = &ServerMock{} +var _ Server = &ServerMock{} // ServerMock is a mock implementation of Server. // @@ -19,7 +18,7 @@ var _ p2p.Server = &ServerMock{} // // make and configure a mocked Server // mockedServer := &ServerMock{ // ServeFunc: func() error { -// panic("mock out the Serve method") +// panic("mock out the Run method") // }, // SetHandleBlocksRequestFunc: func(endpoint string) { // panic("mock out the SetHandleBlocksRequest method") @@ -127,7 +126,7 @@ type ServerMock struct { // Serve calls ServeFunc. func (mock *ServerMock) Serve() error { if mock.ServeFunc == nil { - panic("ServerMock.ServeFunc: method is nil but Server.Serve was just called") + panic("ServerMock.ServeFunc: method is nil but Server.Run was just called") } callInfo := struct { }{} diff --git a/validatornode/settings.json b/validatornode/settings.json new file mode 100644 index 00000000..85d858dc --- /dev/null +++ b/validatornode/settings.json @@ -0,0 +1,37 @@ +{ + "host": { + "ip": "", + "port": 10600 + }, + "network": { + "maxOutboundsCount": 8, + "seeds": [ + "seed-hael.ruthenium.my-cloud.me:10600", + "seed-styx.ruthenium.my-cloud.me:10600" + ], + "synchronizationIntervalInSeconds": 6, + "connectionTimeoutInSeconds": 3 + }, + "protocol": { + "blocksCountLimit": 1440, + "coinDigitsCount": 8, + "genesisAmount": 5000000000000, + "halfLifeInDays": 373.59, + "incomeBase": 100000000000, + "incomeLimit": 5000000000000, + "minimalTransactionFee": 1000, + "validationIntervalInSeconds": 3, + "validationTimeoutInSeconds": 3, + "verificationsCountPerValidation": 6 + }, + "registry": { + "synchronizationIntervalInSeconds": 3600 + }, + "validator": { + "address": "", + "infuraKey": "" + }, + "log": { + "level": "info" + } +}