diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f8319721..a2d317443 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## 0.0.6
+*Jule 16th, 2018*
+
+BREAKING CHANGES
+
+- [core] Change commissions
+- [testnet] New testnet id
+- [core] Fix transaction decoding issue
+- [core] Remove transaction ConvertCoin, add SellCoin and BuyCoin. For details see the docs.
+- [core] Coin name is now limited to max 64 bytes
+- [api] Update estimate exchange endpoint
+
+IMPROVEMENT
+
+- [api] Update transaction api
+- [api] Add transaction result to block api
+- [mempool] Mempool cache is disabled
+- [tendermint] Updated to v0.22.4
+- [versioning] Adapt Semantic Versioning https://semver.org/
+- [client] Add --disable-api flag to client
+
## 0.0.5
*Jule 4rd, 2018*
diff --git a/Gopkg.lock b/Gopkg.lock
index 450bf5c00..6bf7d4c34 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -1,12 +1,6 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
-[[projects]]
- branch = "master"
- name = "github.com/ALTree/floatutils"
- packages = ["."]
- revision = "b176f1e721fc5f9520ce35b8c68c90f78bda8d62"
-
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
@@ -26,7 +20,6 @@
version = "v1.1.0"
[[projects]]
- branch = "master"
name = "github.com/ebuchman/fail-test"
packages = ["."]
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
@@ -156,8 +149,7 @@
"prometheus",
"prometheus/promhttp"
]
- revision = "c5b7fccd204277076155f10851dad72b76a49317"
- version = "v0.8.0"
+ revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632"
[[projects]]
branch = "master"
@@ -187,12 +179,8 @@
revision = "40f013a808ec4fa79def444a1a56de4d1727efcb"
[[projects]]
- branch = "master"
name = "github.com/rcrowley/go-metrics"
- packages = [
- ".",
- "exp"
- ]
+ packages = ["."]
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
[[projects]]
@@ -284,8 +272,8 @@
"types",
"version"
]
- revision = "dfa9a9a30a666132425b29454e90a472aa579a48"
- version = "v0.22.0"
+ revision = "c64a3c74c870d725ba1356f75b4afadf0928c297"
+ version = "v0.22.4"
[[projects]]
branch = "master"
@@ -303,7 +291,6 @@
revision = "b47b1587369238182299fe4dad77d05b8b461e06"
[[projects]]
- branch = "master"
name = "golang.org/x/net"
packages = [
"context",
@@ -315,7 +302,7 @@
"netutil",
"trace"
]
- revision = "1e491301e022f8f977054da4c2d852decd59571f"
+ revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f"
[[projects]]
name = "golang.org/x/text"
@@ -389,6 +376,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "b5767faba6c17d4970ec50b07e9c0f7ba70872b835c1fef51441a4101de8a9b1"
+ inputs-digest = "fe1d4dfba2b86ce6861297f5a51daef78af4bd3489b827d917f19b8f0986e66c"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 555d8059c..5cf3a7ff0 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -24,11 +24,6 @@
# go-tests = true
# unused-packages = true
-
-[[constraint]]
- branch = "master"
- name = "github.com/ALTree/floatutils"
-
[[constraint]]
branch = "master"
name = "github.com/btcsuite/btcd"
@@ -59,7 +54,7 @@
[[constraint]]
name = "github.com/tendermint/tendermint"
- version = "0.22.0"
+ version = "0.22.4"
[[constraint]]
branch = "v2"
diff --git a/README.md b/README.md
index 07670e606..3bc556b21 100644
--- a/README.md
+++ b/README.md
@@ -10,91 +10,32 @@ Minter is a blockchain network that lets people, projects, and companies issue a
_NOTE: This is alpha software. Please contact us if you intend to run it in production._
-## Documentation
-
-For documentation, [Read The Docs](https://minter-go-node.readthedocs.io/en/dev/).
-
-## Run using Docker
-
-You'll need [docker](https://docker.com/) and [docker compose](https://docs.docker.com/compose/) installed.
-
-Clone Minter to your machine
-```bash
-$ git clone https://github.com/MinterTeam/minter-go-node.git
-$ cd minter-go-node
-```
-
-Prepare configs
-```bash
-$ mkdir -p ~/.tendermint/data
-$ mkdir -p ~/.minter/data
-
-$ cp -R networks/testnet/ ~/.tendermint/config
-
-$ chmod -R 0777 ~/.tendermint
-$ chmod -R 0777 ~/.minter
-```
+## Installation
-Start Minter
-```bash
-$ docker-compose up
-```
+You can get official installation instructions in our [docs](https://minter-go-node.readthedocs.io/en/dev/install.html).
-## Build and run manually
-
-You'll need **go** [installed](https://golang.org/doc/install) and the required
-[environment variables set](https://github.com/tendermint/tendermint/wiki/Setting-GOPATH)
-
-1. Install [Tendermint 0.22.0](https://github.com/tendermint/tendermint/blob/master/docs/install.rst)
-
-2. Clone Minter to your machine
-```bash
-$ mkdir $GOPATH/src/github.com/MinterTeam
-$ cd $GOPATH/src/github.com/MinterTeam
-$ git clone https://github.com/MinterTeam/minter-go-node.git
-$ cd minter-go-node
-```
-
-3. Get Tools & Dependencies
-
-```bash
-$ make get_tools
-$ make get_vendor_deps
-```
-
-4. Compile & install
-```bash
-$ make install
-```
-
-5. Create data directories
-```bash
-$ mkdir -p ~/.tendermint/data
-$ mkdir -p ~/.minter/data
-```
+## Documentation
-6. Copy config and genesis file
-```bash
-$ cp -R networks/testnet/ ~/.tendermint/config
-```
+For documentation, [Read The Docs](https://minter-go-node.readthedocs.io/en/dev/).
-7. Run Tendermint
-```bash
-$ tendermint node
-```
+## Versioning
-8. Run Minter
+### SemVer
-```bash
-$ minter
-```
+Minter uses [SemVer](http://semver.org/) to determine when and how the version changes.
+According to SemVer, anything in the public API can change at any time before version 1.0.0
-## Troubleshooting
+To provide some stability to Minter users in these 0.X.X days, the MINOR version is used
+to signal breaking changes across a subset of the total public API. This subset includes all
+interfaces exposed to other processes, but does not include the in-process Go APIs.
-If you see error like this:
+### Upgrades
-```
-ERROR: Failed to create node: Error starting proxy app connections: Error on replay: Wrong Block.Header.AppHash. Expected 6D94BF43BB6C83F396FD8310BC2983F08C658344F9F348BB6675D1E5913230B3, got A2F322A4891092C690F5F0B80C1B9D5017A703035B63385108628EC244ECB191
-```
+In an effort to avoid accumulating technical debt prior to 1.0.0,
+we do not guarantee that breaking changes (ie. bumps in the MINOR version)
+will work with existing tendermint blockchains. In these cases you will
+have to start a new blockchain, or write something custom to get the old
+data into the new chain.
-then your build of Minter Node and network build of Minter Node are different.
\ No newline at end of file
+However, any bump in the PATCH version should be compatible with existing histories
+(if not please open an [issue](https://github.com/MinterTeam/minter-go-node/issues)).
\ No newline at end of file
diff --git a/api/api.go b/api/api.go
index a4c4135a6..19f6708ad 100644
--- a/api/api.go
+++ b/api/api.go
@@ -41,7 +41,8 @@ func RunApi(b *minter.Blockchain) {
router.HandleFunc("/api/transactions", Transactions).Methods("GET")
router.HandleFunc("/api/status", Status).Methods("GET")
router.HandleFunc("/api/coinInfo/{symbol}", GetCoinInfo).Methods("GET")
- router.HandleFunc("/api/estimateCoinExchangeReturn", EstimateCoinExchangeReturn).Methods("GET")
+ router.HandleFunc("/api/estimateCoinSell", EstimateCoinSell).Methods("GET")
+ router.HandleFunc("/api/estimateCoinBuy", EstimateCoinBuy).Methods("GET")
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
diff --git a/api/block.go b/api/block.go
index be48e2173..c8761738d 100644
--- a/api/block.go
+++ b/api/block.go
@@ -24,15 +24,17 @@ type BlockResponse struct {
}
type BlockTransactionResponse struct {
- Hash string `json:"hash"`
- From string `json:"from"`
- Nonce uint64 `json:"nonce"`
- GasPrice *big.Int `json:"gasPrice"`
- Type byte `json:"type"`
- Data transaction.Data `json:"data"`
- Payload []byte `json:"payload"`
- ServiceData []byte `json:"serviceData"`
- Gas int64 `json:"gas"`
+ Hash string `json:"hash"`
+ RawTx string `json:"raw_tx"`
+ From string `json:"from"`
+ Nonce uint64 `json:"nonce"`
+ GasPrice *big.Int `json:"gas_price"`
+ Type byte `json:"type"`
+ Data transaction.Data `json:"data"`
+ Payload []byte `json:"payload"`
+ ServiceData []byte `json:"service_data"`
+ Gas int64 `json:"gas"`
+ TxResult ResponseDeliverTx `json:"tx_result"`
}
func Block(w http.ResponseWriter, r *http.Request) {
@@ -40,7 +42,8 @@ func Block(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
height, _ := strconv.ParseInt(vars["height"], 10, 64)
- result, err := client.Block(&height)
+ block, err := client.Block(&height)
+ blockResults, err := client.BlockResults(&height)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
@@ -55,14 +58,15 @@ func Block(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
- txs := make([]BlockTransactionResponse, len(result.Block.Data.Txs))
+ txs := make([]BlockTransactionResponse, len(block.Block.Data.Txs))
- for i, rawTx := range result.Block.Data.Txs {
+ for i, rawTx := range block.Block.Data.Txs {
tx, _ := transaction.DecodeFromBytes(rawTx)
sender, _ := tx.Sender()
txs[i] = BlockTransactionResponse{
Hash: fmt.Sprintf("Mt%x", types.Tx(rawTx).Hash()),
+ RawTx: fmt.Sprintf("%x", rawTx),
From: sender.String(),
Nonce: tx.Nonce,
GasPrice: tx.GasPrice,
@@ -71,16 +75,25 @@ func Block(w http.ResponseWriter, r *http.Request) {
Payload: tx.Payload,
ServiceData: tx.ServiceData,
Gas: tx.Gas(),
+ TxResult: ResponseDeliverTx{
+ Code: blockResults.Results.DeliverTx[i].Code,
+ Data: blockResults.Results.DeliverTx[i].Data,
+ Log: blockResults.Results.DeliverTx[i].Log,
+ Info: blockResults.Results.DeliverTx[i].Info,
+ GasWanted: blockResults.Results.DeliverTx[i].GasWanted,
+ GasUsed: blockResults.Results.DeliverTx[i].GasUsed,
+ Tags: blockResults.Results.DeliverTx[i].Tags,
+ },
}
}
response := BlockResponse{
- Hash: result.Block.Hash(),
- Height: result.Block.Height,
- Time: result.Block.Time,
- NumTxs: result.Block.NumTxs,
- TotalTxs: result.Block.TotalTxs,
- Precommits: result.Block.LastCommit.Precommits,
+ Hash: block.Block.Hash(),
+ Height: block.Block.Height,
+ Time: block.Block.Time,
+ NumTxs: block.Block.NumTxs,
+ TotalTxs: block.Block.TotalTxs,
+ Precommits: block.Block.LastCommit.Precommits,
Transactions: txs,
}
diff --git a/api/estimate_coin_buy.go b/api/estimate_coin_buy.go
new file mode 100644
index 000000000..8da521323
--- /dev/null
+++ b/api/estimate_coin_buy.go
@@ -0,0 +1,77 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "math/big"
+ "net/http"
+)
+
+func EstimateCoinBuy(w http.ResponseWriter, r *http.Request) {
+
+ cState := GetStateForRequest(r)
+
+ query := r.URL.Query()
+ coinToSell := query.Get("coin_to_sell")
+ coinToBuy := query.Get("coin_to_buy")
+ valueToBuy, _ := big.NewInt(0).SetString(query.Get("value_to_buy"), 10)
+
+ var coinToSellSymbol types.CoinSymbol
+ copy(coinToSellSymbol[:], []byte(coinToSell))
+
+ var coinToBuySymbol types.CoinSymbol
+ copy(coinToBuySymbol[:], []byte(coinToBuy))
+
+ var result *big.Int
+
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+
+ if coinToSell == coinToBuy {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin"),
+ })
+ return
+ }
+
+ if !cState.CoinExists(coinToSellSymbol) {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("Coin to sell not exists"),
+ })
+ return
+ }
+
+ if !cState.CoinExists(coinToBuySymbol) {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("Coin to buy not exists"),
+ })
+ return
+ }
+
+ if coinToSellSymbol == types.GetBaseCoin() {
+ coin := cState.GetStateCoin(coinToBuySymbol).Data()
+ result = formula.CalculatePurchaseAmount(coin.Volume, coin.ReserveBalance, coin.Crr, valueToBuy)
+ } else if coinToBuySymbol == types.GetBaseCoin() {
+ coin := cState.GetStateCoin(coinToSellSymbol).Data()
+ result = formula.CalculateSaleAmount(coin.Volume, coin.ReserveBalance, coin.Crr, valueToBuy)
+ } else {
+ coinFrom := cState.GetStateCoin(coinToSellSymbol).Data()
+ coinTo := cState.GetStateCoin(coinToBuySymbol).Data()
+ baseCoinNeeded := formula.CalculatePurchaseAmount(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, valueToBuy)
+ result = formula.CalculateSaleAmount(coinFrom.Volume, coinFrom.ReserveBalance, coinFrom.Crr, baseCoinNeeded)
+ }
+
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(Response{
+ Code: 0,
+ Result: result.String(),
+ })
+}
diff --git a/api/estimate_coin_exchange_return.go b/api/estimate_coin_exchange_return.go
deleted file mode 100644
index e3a71faaa..000000000
--- a/api/estimate_coin_exchange_return.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package api
-
-import (
- "encoding/json"
- "github.com/MinterTeam/minter-go-node/core/types"
- "github.com/MinterTeam/minter-go-node/formula"
- "math/big"
- "net/http"
-)
-
-func EstimateCoinExchangeReturn(w http.ResponseWriter, r *http.Request) {
-
- cState := GetStateForRequest(r)
-
- query := r.URL.Query()
- fromCoin := query.Get("from_coin")
- toCoin := query.Get("to_coin")
- value, _ := big.NewInt(0).SetString(query.Get("value"), 10)
-
- var fromCoinSymbol types.CoinSymbol
- copy(fromCoinSymbol[:], []byte(fromCoin))
-
- var toCoinSymbol types.CoinSymbol
- copy(toCoinSymbol[:], []byte(toCoin))
-
- var result *big.Int
-
- if fromCoinSymbol == blockchain.BaseCoin {
- coin := cState.GetStateCoin(toCoinSymbol)
- if coin == nil {
- w.WriteHeader(http.StatusNotFound)
- json.NewEncoder(w).Encode(Response{
- Code: 404,
- Result: nil,
- Log: "Coin not found",
- })
- return
- }
- result = formula.CalculatePurchaseReturn(coin.Data().Volume, coin.Data().ReserveBalance, coin.Data().Crr, value)
- } else if toCoinSymbol == blockchain.BaseCoin {
- coin := cState.GetStateCoin(fromCoinSymbol)
- if coin == nil {
- w.WriteHeader(http.StatusNotFound)
- json.NewEncoder(w).Encode(Response{
- Code: 404,
- Result: nil,
- Log: "Coin not found",
- })
- return
- }
- result = formula.CalculateSaleReturn(coin.Data().Volume, coin.Data().ReserveBalance, coin.Data().Crr, value)
- } else {
- coinFrom := cState.GetStateCoin(fromCoinSymbol)
- coinTo := cState.GetStateCoin(toCoinSymbol)
-
- if coinFrom == nil {
- w.WriteHeader(http.StatusNotFound)
- json.NewEncoder(w).Encode(Response{
- Code: 404,
- Result: nil,
- Log: "Coin not found",
- })
- return
- }
-
- if coinTo == nil {
- w.WriteHeader(http.StatusNotFound)
- json.NewEncoder(w).Encode(Response{
- Code: 404,
- Result: nil,
- Log: "Coin not found",
- })
- return
- }
-
- val := formula.CalculateSaleReturn(coinFrom.Data().Volume, coinFrom.Data().ReserveBalance, coinFrom.Data().Crr, value)
- result = formula.CalculatePurchaseReturn(coinTo.Data().Volume, coinTo.Data().ReserveBalance, coinTo.Data().Crr, val)
- }
-
- w.Header().Set("Content-Type", "application/json; charset=UTF-8")
- w.WriteHeader(http.StatusOK)
-
- json.NewEncoder(w).Encode(Response{
- Code: 0,
- Result: result.String(),
- })
-}
diff --git a/api/estimate_coin_sell.go b/api/estimate_coin_sell.go
new file mode 100644
index 000000000..75d2ce8df
--- /dev/null
+++ b/api/estimate_coin_sell.go
@@ -0,0 +1,77 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "math/big"
+ "net/http"
+)
+
+func EstimateCoinSell(w http.ResponseWriter, r *http.Request) {
+
+ cState := GetStateForRequest(r)
+
+ query := r.URL.Query()
+ coinToSell := query.Get("coin_to_sell")
+ coinToBuy := query.Get("coin_to_buy")
+ valueToSell, _ := big.NewInt(0).SetString(query.Get("value_to_sell"), 10)
+
+ var coinToSellSymbol types.CoinSymbol
+ copy(coinToSellSymbol[:], []byte(coinToSell))
+
+ var coinToBuySymbol types.CoinSymbol
+ copy(coinToBuySymbol[:], []byte(coinToBuy))
+
+ var result *big.Int
+
+ w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+
+ if coinToSell == coinToBuy {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin"),
+ })
+ return
+ }
+
+ if !cState.CoinExists(coinToSellSymbol) {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("Coin to sell not exists"),
+ })
+ return
+ }
+
+ if !cState.CoinExists(coinToBuySymbol) {
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("Coin to buy not exists"),
+ })
+ return
+ }
+
+ if coinToSellSymbol == types.GetBaseCoin() {
+ coin := cState.GetStateCoin(coinToBuySymbol).Data()
+ result = formula.CalculatePurchaseReturn(coin.Volume, coin.ReserveBalance, coin.Crr, valueToSell)
+ } else if coinToBuySymbol == types.GetBaseCoin() {
+ coin := cState.GetStateCoin(coinToSellSymbol).Data()
+ result = formula.CalculateSaleReturn(coin.Volume, coin.ReserveBalance, coin.Crr, valueToSell)
+ } else {
+ coinFrom := cState.GetStateCoin(coinToSellSymbol).Data()
+ coinTo := cState.GetStateCoin(coinToBuySymbol).Data()
+ basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume, coinFrom.ReserveBalance, coinFrom.Crr, valueToSell)
+ result = formula.CalculatePurchaseReturn(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, basecoinValue)
+ }
+
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(Response{
+ Code: 0,
+ Result: result.String(),
+ })
+}
diff --git a/api/transaction.go b/api/transaction.go
index f1ed0bedd..43adabf1c 100644
--- a/api/transaction.go
+++ b/api/transaction.go
@@ -3,7 +3,10 @@ package api
import (
"encoding/hex"
"encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/transaction"
"github.com/gorilla/mux"
+ "github.com/tendermint/tendermint/libs/common"
"net/http"
"strings"
)
@@ -14,7 +17,7 @@ func Transaction(w http.ResponseWriter, r *http.Request) {
hash := strings.TrimLeft(vars["hash"], "Mt")
decoded, err := hex.DecodeString(hash)
- result, err := client.Tx(decoded, false)
+ tx, err := client.Tx(decoded, false)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
@@ -29,9 +32,32 @@ func Transaction(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
+ decodedTx, _ := transaction.DecodeFromBytes(tx.Tx)
+ sender, _ := decodedTx.Sender()
+
err = json.NewEncoder(w).Encode(Response{
- Code: 0,
- Result: result,
+ Code: 0,
+ Result: TransactionResponse{
+ Hash: common.HexBytes(tx.Tx.Hash()),
+ RawTx: fmt.Sprintf("%x", tx.Tx),
+ Height: tx.Height,
+ Index: tx.Index,
+ TxResult: ResponseDeliverTx{
+ Code: tx.TxResult.Code,
+ Data: tx.TxResult.Data,
+ Log: tx.TxResult.Log,
+ Info: tx.TxResult.Info,
+ GasWanted: tx.TxResult.GasWanted,
+ GasUsed: tx.TxResult.GasUsed,
+ Tags: tx.TxResult.Tags,
+ },
+ From: sender.String(),
+ Nonce: decodedTx.Nonce,
+ GasPrice: decodedTx.GasPrice,
+ Type: decodedTx.Type,
+ Data: decodedTx.GetDecodedData(),
+ Payload: decodedTx.Payload,
+ },
})
if err != nil {
diff --git a/api/transactions.go b/api/transactions.go
index b56ad60f3..2a47e9ba1 100644
--- a/api/transactions.go
+++ b/api/transactions.go
@@ -2,6 +2,7 @@ package api
import (
"encoding/json"
+ "fmt"
"github.com/MinterTeam/minter-go-node/core/transaction"
"github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/rpc/core/types"
@@ -11,12 +12,13 @@ import (
type TransactionResponse struct {
Hash common.HexBytes `json:"hash"`
+ RawTx string `json:"raw_tx"`
Height int64 `json:"height"`
Index uint32 `json:"index"`
TxResult ResponseDeliverTx `json:"tx_result"`
From string `json:"from"`
Nonce uint64 `json:"nonce"`
- GasPrice *big.Int `json:"gasPrice"`
+ GasPrice *big.Int `json:"gas_price"`
Type byte `json:"type"`
Data transaction.Data `json:"data"`
Payload []byte `json:"payload"`
@@ -27,8 +29,8 @@ type ResponseDeliverTx struct {
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
Log string `protobuf:"bytes,3,opt,name=log,proto3" json:"log,omitempty"`
Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"`
- GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gasWanted,proto3" json:"gas_wanted,omitempty"`
- GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"`
+ GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gas_wanted,proto3" json:"gas_wanted,omitempty"`
+ GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gas_used,proto3" json:"gas_used,omitempty"`
Tags []common.KVPair `protobuf:"bytes,7,rep,name=tags" json:"tags,omitempty"`
Fee common.KI64Pair `protobuf:"bytes,8,opt,name=fee" json:"fee"`
}
@@ -65,6 +67,7 @@ func Transactions(w http.ResponseWriter, r *http.Request) {
result[i] = TransactionResponse{
Hash: common.HexBytes(tx.Tx.Hash()),
+ RawTx: fmt.Sprintf("%x", tx.Tx),
Height: tx.Height,
Index: tx.Index,
TxResult: ResponseDeliverTx{
@@ -74,6 +77,7 @@ func Transactions(w http.ResponseWriter, r *http.Request) {
Info: tx.TxResult.Info,
GasWanted: tx.TxResult.GasWanted,
GasUsed: tx.TxResult.GasUsed,
+ Tags: tx.TxResult.Tags,
},
From: sender.String(),
Nonce: decodedTx.Nonce,
diff --git a/cmd/minter/main.go b/cmd/minter/main.go
index 97c7c1f11..c6010303d 100644
--- a/cmd/minter/main.go
+++ b/cmd/minter/main.go
@@ -4,28 +4,27 @@ import (
"github.com/MinterTeam/minter-go-node/api"
"github.com/MinterTeam/minter-go-node/cmd/utils"
"github.com/MinterTeam/minter-go-node/core/minter"
+ "github.com/MinterTeam/minter-go-node/log"
"github.com/tendermint/tendermint/abci/server"
"github.com/tendermint/tendermint/libs/common"
- "github.com/tendermint/tendermint/libs/log"
- "os"
)
func main() {
- logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
-
- app := minter.NewMinterBlockchain(logger)
+ app := minter.NewMinterBlockchain()
// Start the listener
srv, err := server.NewServer(*utils.MinterAppAddrFlag, "socket", app)
if err != nil {
panic(err)
}
- srv.SetLogger(logger.With("module", "abci-server"))
+ srv.SetLogger(log.With("module", "abci-server"))
if err := srv.Start(); err != nil {
panic(err)
}
- go api.RunApi(app)
+ if !*utils.DisableApi {
+ go api.RunApi(app)
+ }
// Wait forever
common.TrapSignal(func() {
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index a501b8ab7..a4140091f 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -11,6 +11,7 @@ var (
MinterAppAddrFlag = flag.String("minter_addr", "tcp://0.0.0.0:46658", "This is the address that minter will use to open ABCI application server. Please provide a port.")
MinterAPIAddrFlag = flag.String("api_addr", ":8841", "This is the address that minter will use to open API server. Please provide a port.")
MinterHome = flag.String("home", "", "Path to minter data directory")
+ DisableApi = flag.Bool("disable-api", false, "")
)
func init() {
diff --git a/core/check/check.go b/core/check/check.go
index 12dd1a4da..739a3e1e8 100644
--- a/core/check/check.go
+++ b/core/check/check.go
@@ -27,7 +27,7 @@ type Check struct {
}
func (check *Check) Sender() (types.Address, error) {
- return recoverPlain(check.Hash(), check.R, check.S, check.V, true)
+ return recoverPlain(check.Hash(), check.R, check.S, check.V)
}
func (check *Check) LockPubKey() ([]byte, error) {
@@ -85,12 +85,12 @@ func rlpHash(x interface{}) (h types.Hash) {
return h
}
-func recoverPlain(sighash types.Hash, R, S, Vb *big.Int, homestead bool) (types.Address, error) {
+func recoverPlain(sighash types.Hash, R, S, Vb *big.Int) (types.Address, error) {
if Vb.BitLen() > 8 {
return types.Address{}, ErrInvalidSig
}
V := byte(Vb.Uint64() - 27)
- if !crypto.ValidateSignatureValues(V, R, S, homestead) {
+ if !crypto.ValidateSignatureValues(V, R, S) {
return types.Address{}, ErrInvalidSig
}
// encode the snature in uncompressed format
diff --git a/core/code/code.go b/core/code/code.go
index 2bb97212a..e0c5df7cf 100644
--- a/core/code/code.go
+++ b/core/code/code.go
@@ -18,6 +18,7 @@ const (
CoinAlreadyExists uint32 = 201
WrongCrr uint32 = 202
InvalidCoinSymbol uint32 = 203
+ InvalidCoinName uint32 = 204
// convert
CrossConvert uint32 = 301
diff --git a/core/commissions/commissions.go b/core/commissions/commissions.go
index 8f0acbf9e..13ce845ff 100644
--- a/core/commissions/commissions.go
+++ b/core/commissions/commissions.go
@@ -1,15 +1,15 @@
package commissions
-// all commissions are divided by 10^8
-// actual commission is SendTx * 10^8 = 100 000 000 000 PIP = 0,0000001 BIP
+// all commissions are divided by 10^15
+// actual commission is SendTx * 10^15 = 1 000 000 000 000 000 PIP = 0,001 BIP
const (
- SendTx int64 = 1000
- ConvertTx int64 = 10000
- CreateTx int64 = 100000
- DeclareCandidacyTx int64 = 100000
- DelegateTx int64 = 10000
- UnboundTx int64 = 10000
- RedeemCheckTx int64 = 1000
- PayloadByte int64 = 500
- ToggleCandidateStatus int64 = 1000
+ SendTx int64 = 10
+ ConvertTx int64 = 1000
+ CreateTx int64 = 1000
+ DeclareCandidacyTx int64 = 10000
+ DelegateTx int64 = 100
+ UnbondTx int64 = 100
+ RedeemCheckTx int64 = 10
+ PayloadByte int64 = 2
+ ToggleCandidateStatus int64 = 100
)
diff --git a/core/minter/minter.go b/core/minter/minter.go
index 74b29ea0f..bbcab6717 100644
--- a/core/minter/minter.go
+++ b/core/minter/minter.go
@@ -5,7 +5,6 @@ import (
"encoding/binary"
"encoding/json"
"github.com/MinterTeam/minter-go-node/cmd/utils"
- "github.com/MinterTeam/minter-go-node/core/code"
"github.com/MinterTeam/minter-go-node/core/rewards"
"github.com/MinterTeam/minter-go-node/core/state"
"github.com/MinterTeam/minter-go-node/core/transaction"
@@ -15,7 +14,6 @@ import (
"github.com/MinterTeam/minter-go-node/helpers"
"github.com/MinterTeam/minter-go-node/mintdb"
abciTypes "github.com/tendermint/tendermint/abci/types"
- "github.com/tendermint/tendermint/libs/log"
"github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/rpc/lib/client"
"math/big"
@@ -32,7 +30,6 @@ type Blockchain struct {
rewards *big.Int
activeValidators abciTypes.Validators
validatorsStatuses map[string]int8
- logger log.Logger
BaseCoin types.CoinSymbol
}
@@ -43,10 +40,6 @@ const (
stateTableId = "state"
appTableId = "app"
-
- maxTxLength = 1024
- maxPayloadLength = 128
- maxServiceDataLength = 128
)
var (
@@ -56,7 +49,7 @@ var (
airdropAddress = types.HexToAddress("Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f")
)
-func NewMinterBlockchain(logger log.Logger) *Blockchain {
+func NewMinterBlockchain() *Blockchain {
db, err := mintdb.NewLDBDatabase(utils.GetMinterHome()+"/data", 1000, 1000)
@@ -67,7 +60,6 @@ func NewMinterBlockchain(logger log.Logger) *Blockchain {
blockchain = &Blockchain{
db: db,
BaseCoin: types.GetBaseCoin(),
- logger: logger,
}
blockchain.updateCurrentRootHash()
@@ -236,36 +228,7 @@ func (app *Blockchain) Info(req abciTypes.RequestInfo) (resInfo abciTypes.Respon
}
func (app *Blockchain) DeliverTx(rawTx []byte) abciTypes.ResponseDeliverTx {
-
- if len(rawTx) > maxTxLength {
- return abciTypes.ResponseDeliverTx{
- Code: code.TxTooLarge,
- Log: "TX length is over 1024 bytes"}
- }
-
- tx, err := transaction.DecodeFromBytes(rawTx)
-
- if err != nil {
- return abciTypes.ResponseDeliverTx{
- Code: code.DecodeError,
- Log: err.Error()}
- }
-
- if len(tx.Payload) > maxPayloadLength {
- return abciTypes.ResponseDeliverTx{
- Code: code.TxPayloadTooLarge,
- Log: "TX payload length is over 128 bytes"}
- }
-
- if len(tx.ServiceData) > maxServiceDataLength {
- return abciTypes.ResponseDeliverTx{
- Code: code.TxServiceDataTooLarge,
- Log: "TX service data length is over 128 bytes"}
- }
-
- app.logger.Info("Deliver tx", "tx", tx.String())
-
- response := transaction.RunTx(app.stateDeliver, false, tx, app.rewards, app.height)
+ response := transaction.RunTx(app.stateDeliver, false, rawTx, app.rewards, app.height)
return abciTypes.ResponseDeliverTx{
Code: response.Code,
@@ -280,34 +243,7 @@ func (app *Blockchain) DeliverTx(rawTx []byte) abciTypes.ResponseDeliverTx {
}
func (app *Blockchain) CheckTx(rawTx []byte) abciTypes.ResponseCheckTx {
-
- if len(rawTx) > maxTxLength {
- return abciTypes.ResponseCheckTx{
- Code: code.TxTooLarge,
- Log: "TX length is over 1024 bytes"}
- }
-
- tx, err := transaction.DecodeFromBytes(rawTx)
-
- if err != nil {
- return abciTypes.ResponseCheckTx{
- Code: code.DecodeError,
- Log: err.Error()}
- }
-
- if len(tx.Payload) > maxPayloadLength {
- return abciTypes.ResponseCheckTx{
- Code: code.TxPayloadTooLarge,
- Log: "TX payload length is over 128 bytes"}
- }
-
- if len(tx.ServiceData) > maxServiceDataLength {
- return abciTypes.ResponseCheckTx{
- Code: code.TxServiceDataTooLarge,
- Log: "TX service data length is over 128 bytes"}
- }
-
- response := transaction.RunTx(app.stateCheck, true, tx, nil, app.height)
+ response := transaction.RunTx(app.stateCheck, true, rawTx, nil, app.height)
return abciTypes.ResponseCheckTx{
Code: response.Code,
diff --git a/core/transaction/buy_coin.go b/core/transaction/buy_coin.go
new file mode 100644
index 000000000..1161f8e55
--- /dev/null
+++ b/core/transaction/buy_coin.go
@@ -0,0 +1,167 @@
+package transaction
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/tendermint/tendermint/libs/common"
+ "math/big"
+)
+
+type BuyCoinData struct {
+ CoinToBuy types.CoinSymbol
+ ValueToBuy *big.Int
+ CoinToSell types.CoinSymbol
+}
+
+func (data BuyCoinData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ CoinToBuy types.CoinSymbol `json:"coin_to_buy,string"`
+ ValueToBuy string `json:"value_to_buy"`
+ CoinToSell types.CoinSymbol `json:"coin_to_sell,string"`
+ }{
+ CoinToBuy: data.CoinToBuy,
+ ValueToBuy: data.ValueToBuy.String(),
+ CoinToSell: data.CoinToSell,
+ })
+}
+
+func (data BuyCoinData) String() string {
+ return fmt.Sprintf("BUY COIN sell:%s buy:%s %s",
+ data.CoinToSell.String(), data.ValueToBuy.String(), data.CoinToBuy.String())
+}
+
+func (data BuyCoinData) Gas() int64 {
+ return commissions.ConvertTx
+}
+
+func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ if data.CoinToSell == data.CoinToBuy {
+ return Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin")}
+ }
+
+ if !context.CoinExists(data.CoinToSell) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ if !context.CoinExists(data.CoinToBuy) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if data.CoinToSell != types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToSell)
+
+ if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
+ return Response{
+ Code: code.CoinReserveNotSufficient,
+ Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
+ }
+
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ var value *big.Int
+
+ if data.CoinToSell == types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToBuy).Data()
+
+ value = formula.CalculatePurchaseAmount(coin.Volume, coin.ReserveBalance, coin.Crr, data.ValueToBuy)
+
+ totalTxCost := big.NewInt(0).Add(value, commission)
+ if context.GetBalance(sender, data.CoinToSell).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ context.SubBalance(sender, data.CoinToSell, value)
+ context.AddCoinVolume(data.CoinToBuy, data.ValueToBuy)
+ context.AddCoinReserve(data.CoinToBuy, value)
+ }
+ } else if data.CoinToBuy == types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToSell).Data()
+
+ value = formula.CalculateSaleAmount(coin.Volume, coin.ReserveBalance, coin.Crr, data.ValueToBuy)
+
+ totalTxCost := big.NewInt(0).Add(value, commission)
+ if context.GetBalance(sender, data.CoinToSell).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ context.SubBalance(sender, data.CoinToSell, value)
+ context.SubCoinVolume(data.CoinToSell, value)
+ context.SubCoinReserve(data.CoinToSell, data.ValueToBuy)
+ }
+ } else {
+ coinFrom := context.GetStateCoin(data.CoinToSell).Data()
+ coinTo := context.GetStateCoin(data.CoinToBuy).Data()
+
+ baseCoinNeeded := formula.CalculatePurchaseAmount(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, data.ValueToBuy)
+ value = formula.CalculateSaleAmount(coinFrom.Volume, coinFrom.ReserveBalance, coinFrom.Crr, baseCoinNeeded)
+
+ totalTxCost := big.NewInt(0).Add(value, commission)
+ if context.GetBalance(sender, data.CoinToSell).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ context.SubBalance(sender, data.CoinToSell, value)
+
+ context.AddCoinVolume(data.CoinToBuy, data.ValueToBuy)
+ context.SubCoinVolume(data.CoinToSell, value)
+
+ context.AddCoinReserve(data.CoinToBuy, baseCoinNeeded)
+ context.SubCoinReserve(data.CoinToSell, baseCoinNeeded)
+ }
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commissionInBaseCoin)
+
+ context.SubBalance(sender, data.CoinToSell, commission)
+
+ if data.CoinToSell != types.GetBaseCoin() {
+ context.SubCoinVolume(data.CoinToSell, commission)
+ context.SubCoinReserve(data.CoinToSell, commissionInBaseCoin)
+ }
+
+ context.AddBalance(sender, data.CoinToBuy, value)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ tags := common.KVPairs{
+ common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeBuyCoin}},
+ common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
+ common.KVPair{Key: []byte("tx.coin_to_buy"), Value: []byte(data.CoinToBuy.String())},
+ common.KVPair{Key: []byte("tx.coin_to_sell"), Value: []byte(data.CoinToSell.String())},
+ common.KVPair{Key: []byte("tx.return"), Value: value.Bytes()},
+ }
+
+ return Response{
+ Code: code.OK,
+ Tags: tags,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/create_coin.go b/core/transaction/create_coin.go
new file mode 100644
index 000000000..af2eb3c3d
--- /dev/null
+++ b/core/transaction/create_coin.go
@@ -0,0 +1,131 @@
+package transaction
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/tendermint/tendermint/libs/common"
+ "math/big"
+ "regexp"
+)
+
+const maxCoinNameBytes = 64
+const allowedCoinSymbols = "^[A-Z0-9]{3,10}$"
+
+type CreateCoinData struct {
+ Name string
+ Symbol types.CoinSymbol
+ InitialAmount *big.Int
+ InitialReserve *big.Int
+ ConstantReserveRatio uint
+}
+
+func (data CreateCoinData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ Name string `json:"name"`
+ Symbol types.CoinSymbol `json:"coin_symbol"`
+ InitialAmount string `json:"initial_amount"`
+ InitialReserve string `json:"initial_reserve"`
+ ConstantReserveRatio uint `json:"constant_reserve_ratio"`
+ }{
+ Name: data.Name,
+ Symbol: data.Symbol,
+ InitialAmount: data.InitialAmount.String(),
+ InitialReserve: data.InitialReserve.String(),
+ ConstantReserveRatio: data.ConstantReserveRatio,
+ })
+}
+
+func (data CreateCoinData) String() string {
+ return fmt.Sprintf("CREATE COIN symbol:%s reserve:%s amount:%s crr:%d",
+ data.Symbol.String(), data.InitialReserve, data.InitialAmount, data.ConstantReserveRatio)
+}
+
+func (data CreateCoinData) Gas() int64 {
+ return commissions.CreateTx
+}
+
+func (data CreateCoinData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ if len(data.Name) > maxCoinNameBytes {
+ return Response{
+ Code: code.InvalidCoinName,
+ Log: fmt.Sprintf("Coin name is invalid. Allowed up to %d bytes.", maxCoinNameBytes)}
+ }
+
+ if match, _ := regexp.MatchString(allowedCoinSymbols, data.Symbol.String()); !match {
+ return Response{
+ Code: code.InvalidCoinSymbol,
+ Log: fmt.Sprintf("Invalid coin symbol. Should be %s", allowedCoinSymbols)}
+ }
+
+ commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commission.Mul(commission, CommissionMultiplier)
+
+ // compute additional price from letters count
+ lettersCount := len(data.Symbol.String())
+ var price int64 = 0
+ switch lettersCount {
+ case 3:
+ price += 1000000 // 1mln bips
+ case 4:
+ price += 100000 // 100k bips
+ case 5:
+ price += 10000 // 10k bips
+ case 6:
+ price += 1000 // 1k bips
+ case 7:
+ price += 100 // 100 bips
+ case 8:
+ price += 10 // 10 bips
+ }
+ p := big.NewInt(10)
+ p.Exp(p, big.NewInt(18), nil)
+ p.Mul(p, big.NewInt(price))
+ commission.Add(commission, p)
+
+ totalTxCost := big.NewInt(0).Add(data.InitialReserve, commission)
+
+ if context.GetBalance(sender, types.GetBaseCoin()).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if context.CoinExists(data.Symbol) {
+ return Response{
+ Code: code.CoinAlreadyExists,
+ Log: fmt.Sprintf("Coin already exists")}
+ }
+
+ if data.ConstantReserveRatio < 10 || data.ConstantReserveRatio > 100 {
+ return Response{
+ Code: code.WrongCrr,
+ Log: fmt.Sprintf("Constant Reserve Ratio should be between 10 and 100")}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, types.GetBaseCoin(), totalTxCost)
+ context.CreateCoin(data.Symbol, data.Name, data.InitialAmount, data.ConstantReserveRatio, data.InitialReserve, sender)
+ context.AddBalance(sender, data.Symbol, data.InitialAmount)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ tags := common.KVPairs{
+ common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeCreateCoin}},
+ common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
+ common.KVPair{Key: []byte("tx.coin"), Value: []byte(data.Symbol.String())},
+ }
+
+ return Response{
+ Code: code.OK,
+ Tags: tags,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/declare_candidacy.go b/core/transaction/declare_candidacy.go
new file mode 100644
index 000000000..72a5cfa4f
--- /dev/null
+++ b/core/transaction/declare_candidacy.go
@@ -0,0 +1,109 @@
+package transaction
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/MinterTeam/minter-go-node/hexutil"
+ "math/big"
+)
+
+const minCommission = 0
+const maxCommission = 100
+
+type DeclareCandidacyData struct {
+ Address types.Address
+ PubKey []byte
+ Commission uint
+ Coin types.CoinSymbol
+ Stake *big.Int
+}
+
+func (data DeclareCandidacyData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ Address types.Address `json:"address"`
+ PubKey string `json:"pub_key"`
+ Commission uint `json:"commission"`
+ Coin types.CoinSymbol `json:"coin"`
+ Stake string `json:"stake"`
+ }{
+ Address: data.Address,
+ PubKey: fmt.Sprintf("Mp%x", data.PubKey),
+ Commission: data.Commission,
+ Coin: data.Coin,
+ Stake: data.Stake.String(),
+ })
+}
+
+func (data DeclareCandidacyData) String() string {
+ return fmt.Sprintf("DECLARE CANDIDACY address:%s pubkey:%s commission: %d ",
+ data.Address.String(), hexutil.Encode(data.PubKey[:]), data.Commission)
+}
+
+func (data DeclareCandidacyData) Gas() int64 {
+ return commissions.DeclareCandidacyTx
+}
+
+func (data DeclareCandidacyData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ if len(data.PubKey) != 32 {
+ return Response{
+ Code: code.IncorrectPubKey,
+ Log: fmt.Sprintf("Incorrect PubKey")}
+ }
+
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if data.Coin != types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.Coin)
+
+ if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
+ return Response{
+ Code: code.CoinReserveNotSufficient,
+ Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
+ }
+
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ totalTxCost := big.NewInt(0).Add(data.Stake, commission)
+
+ if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if context.CandidateExists(data.PubKey) {
+ return Response{
+ Code: code.CandidateExists,
+ Log: fmt.Sprintf("Candidate with such public key (%x) already exists", data.PubKey)}
+ }
+
+ if data.Commission < minCommission || data.Commission > maxCommission {
+ return Response{
+ Code: code.WrongCommission,
+ Log: fmt.Sprintf("Commission should be between 0 and 100")}
+ }
+
+ // TODO: limit number of candidates to prevent flooding
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, data.Coin, totalTxCost)
+ context.CreateCandidate(data.Address, data.PubKey, data.Commission, uint(currentBlock), data.Coin, data.Stake)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ return Response{
+ Code: code.OK,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/delegate.go b/core/transaction/delegate.go
new file mode 100644
index 000000000..533db5c2c
--- /dev/null
+++ b/core/transaction/delegate.go
@@ -0,0 +1,86 @@
+package transaction
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/MinterTeam/minter-go-node/hexutil"
+ "math/big"
+)
+
+type DelegateData struct {
+ PubKey []byte
+ Coin types.CoinSymbol
+ Stake *big.Int
+}
+
+func (data DelegateData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ PubKey string `json:"pub_key"`
+ Coin types.CoinSymbol `json:"coin"`
+ Stake string `json:"stake"`
+ }{
+ PubKey: fmt.Sprintf("Mp%x", data.PubKey),
+ Coin: data.Coin,
+ Stake: data.Stake.String(),
+ })
+}
+
+func (data DelegateData) String() string {
+ return fmt.Sprintf("DELEGATE ubkey:%s ",
+ hexutil.Encode(data.PubKey[:]))
+}
+
+func (data DelegateData) Gas() int64 {
+ return commissions.DelegateTx
+}
+
+func (data DelegateData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if data.Coin != types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.Coin)
+
+ if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
+ return Response{
+ Code: code.CoinReserveNotSufficient,
+ Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
+ }
+
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ totalTxCost := big.NewInt(0).Add(data.Stake, commission)
+
+ if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !context.CandidateExists(data.PubKey) {
+ return Response{
+ Code: code.CandidateNotFound,
+ Log: fmt.Sprintf("Candidate with such public key not found")}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, data.Coin, totalTxCost)
+ context.Delegate(sender, data.PubKey, data.Coin, data.Stake)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ return Response{
+ Code: code.OK,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/executor.go b/core/transaction/executor.go
index bc1bad757..b6b81b6d3 100644
--- a/core/transaction/executor.go
+++ b/core/transaction/executor.go
@@ -1,24 +1,22 @@
package transaction
import (
- "bytes"
- "encoding/hex"
"fmt"
- "github.com/MinterTeam/minter-go-node/core/check"
"github.com/MinterTeam/minter-go-node/core/code"
"github.com/MinterTeam/minter-go-node/core/state"
- "github.com/MinterTeam/minter-go-node/core/types"
- "github.com/MinterTeam/minter-go-node/crypto"
- "github.com/MinterTeam/minter-go-node/crypto/sha3"
- "github.com/MinterTeam/minter-go-node/formula"
- "github.com/MinterTeam/minter-go-node/rlp"
+ "github.com/MinterTeam/minter-go-node/log"
"github.com/tendermint/tendermint/libs/common"
"math/big"
- "regexp"
)
var (
- CommissionMultiplier = big.NewInt(10e8)
+ CommissionMultiplier = big.NewInt(10e15)
+)
+
+const (
+ maxTxLength = 1024
+ maxPayloadLength = 128
+ maxServiceDataLength = 128
)
type Response struct {
@@ -32,623 +30,56 @@ type Response struct {
Fee common.KI64Pair `protobuf:"bytes,8,opt,name=fee" json:"fee"`
}
-func RunTx(context *state.StateDB, isCheck bool, tx *Transaction, rewardPull *big.Int, currentBlock uint64) Response {
- sender, _ := tx.Sender()
-
- // do not look at nonce of transaction while checking tx
- // this will allow us to send multiple transaction from one account in one block
- // in the future we should use "last known nonce" approach from Ethereum
- if !isCheck {
- if expectedNonce := context.GetNonce(sender) + 1; expectedNonce != tx.Nonce {
- return Response{
- Code: code.WrongNonce,
- Log: fmt.Sprintf("Unexpected nonce. Expected: %d, got %d.", expectedNonce, tx.Nonce)}
- }
- }
+func RunTx(context *state.StateDB, isCheck bool, rawTx []byte, rewardPull *big.Int, currentBlock uint64) Response {
- if len(tx.Payload)+len(tx.ServiceData) > 1024 {
+ if len(rawTx) > maxTxLength {
return Response{
- Code: code.TooLongPayload,
- Log: fmt.Sprintf("Too long Payload + ServiceData. Max 1024 bytes.")}
+ Code: code.TxTooLarge,
+ Log: "TX length is over 1024 bytes"}
}
- switch tx.Type {
- case TypeDeclareCandidacy:
-
- data := tx.GetDecodedData().(DeclareCandidacyData)
-
- if len(data.PubKey) != 32 {
- return Response{
- Code: code.IncorrectPubKey,
- Log: fmt.Sprintf("Incorrect PubKey")}
- }
-
- commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
- commission := big.NewInt(0).Set(commissionInBaseCoin)
-
- if data.Coin != types.GetBaseCoin() {
- coin := context.GetStateCoin(data.Coin)
-
- if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
- return Response{
- Code: code.CoinReserveNotSufficient,
- Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
- }
-
- commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
- }
-
- totalTxCost := big.NewInt(0).Add(data.Stake, commission)
-
- if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
- }
-
- if context.CandidateExists(data.PubKey) {
- return Response{
- Code: code.CandidateExists,
- Log: fmt.Sprintf("Candidate with such public key (%x) already exists", data.PubKey)}
- }
-
- if data.Commission < 0 || data.Commission > 100 {
- return Response{
- Code: code.WrongCommission,
- Log: fmt.Sprintf("Commission should be between 0 and 100")}
- }
-
- // TODO: limit number of candidates to prevent flooding
-
- if !isCheck {
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, data.Coin, totalTxCost)
- context.CreateCandidate(data.Address, data.PubKey, data.Commission, uint(currentBlock), data.Coin, data.Stake)
- context.SetNonce(sender, tx.Nonce)
- }
-
- return Response{
- Code: code.OK,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
- case TypeSetCandidateOnline:
-
- data := tx.GetDecodedData().(SetCandidateOnData)
-
- commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commission.Mul(commission, CommissionMultiplier)
-
- if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
- }
-
- if !context.CandidateExists(data.PubKey) {
- return Response{
- Code: code.CandidateNotFound,
- Log: fmt.Sprintf("Candidate with such public key (%x) not found", data.PubKey)}
- }
-
- candidate := context.GetStateCandidate(data.PubKey)
-
- if bytes.Compare(candidate.CandidateAddress.Bytes(), sender.Bytes()) != 0 {
- return Response{
- Code: code.IsNotOwnerOfCandidate,
- Log: fmt.Sprintf("Sender is not an owner of a candidate")}
- }
-
- if !isCheck {
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, types.GetBaseCoin(), commission)
- context.SetCandidateOnline(data.PubKey)
- context.SetNonce(sender, tx.Nonce)
- }
-
- return Response{
- Code: code.OK,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
- case TypeSetCandidateOffline:
-
- data := tx.GetDecodedData().(SetCandidateOffData)
-
- commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commission.Mul(commission, CommissionMultiplier)
-
- if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
- }
-
- if !context.CandidateExists(data.PubKey) {
- return Response{
- Code: code.CandidateNotFound,
- Log: fmt.Sprintf("Candidate with such public key not found")}
- }
-
- candidate := context.GetStateCandidate(data.PubKey)
-
- if bytes.Compare(candidate.CandidateAddress.Bytes(), sender.Bytes()) != 0 {
- return Response{
- Code: code.IsNotOwnerOfCandidate,
- Log: fmt.Sprintf("Sender is not an owner of a candidate")}
- }
-
- if !isCheck {
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, types.GetBaseCoin(), commission)
- context.SetCandidateOffline(data.PubKey)
- context.SetNonce(sender, tx.Nonce)
- }
-
- return Response{
- Code: code.OK,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
- case TypeDelegate:
-
- data := tx.GetDecodedData().(DelegateData)
-
- commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
- commission := big.NewInt(0).Set(commissionInBaseCoin)
-
- if data.Coin != types.GetBaseCoin() {
- coin := context.GetStateCoin(data.Coin)
-
- if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
- return Response{
- Code: code.CoinReserveNotSufficient,
- Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
- }
-
- commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
- }
-
- totalTxCost := big.NewInt(0).Add(data.Stake, commission)
-
- if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
- }
-
- if !context.CandidateExists(data.PubKey) {
- return Response{
- Code: code.CandidateNotFound,
- Log: fmt.Sprintf("Candidate with such public key not found")}
- }
-
- if !isCheck {
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, data.Coin, totalTxCost)
- context.Delegate(sender, data.PubKey, data.Coin, data.Stake)
- context.SetNonce(sender, tx.Nonce)
- }
-
- return Response{
- Code: code.OK,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
- case TypeUnbond:
-
- data := tx.GetDecodedData().(UnbondData)
-
- commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commission.Mul(commission, CommissionMultiplier)
-
- if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
- }
-
- if !context.CandidateExists(data.PubKey) {
- return Response{
- Code: code.CandidateNotFound,
- Log: fmt.Sprintf("Candidate with such public key not found")}
- }
-
- candidate := context.GetStateCandidate(data.PubKey)
-
- stake := candidate.GetStakeOfAddress(sender, data.Coin)
-
- if stake == nil {
- return Response{
- Code: code.StakeNotFound,
- Log: fmt.Sprintf("Stake of current user not found")}
- }
+ tx, err := DecodeFromBytes(rawTx)
- if stake.Value.Cmp(data.Value) < 0 {
- return Response{
- Code: code.InsufficientStake,
- Log: fmt.Sprintf("Insufficient stake for sender account")}
- }
-
- if !isCheck {
- // now + 31 days
- unboundAtBlock := currentBlock + 518400
-
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, types.GetBaseCoin(), commission)
- context.SubStake(sender, data.PubKey, data.Coin, data.Value)
- context.GetOrNewStateFrozenFunds(unboundAtBlock).AddFund(sender, data.PubKey, data.Coin, data.Value)
- context.SetNonce(sender, tx.Nonce)
- }
+ if !isCheck {
+ log.Info("Deliver tx", "tx", tx.String())
+ }
+ if err != nil {
return Response{
- Code: code.OK,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
- case TypeSend:
-
- data := tx.GetDecodedData().(SendData)
-
- if !context.CoinExists(data.Coin) {
- return Response{
- Code: code.CoinNotExists,
- Log: fmt.Sprintf("Coin not exists")}
- }
-
- commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
- commission := big.NewInt(0).Set(commissionInBaseCoin)
-
- if data.Coin != types.GetBaseCoin() {
- coin := context.GetStateCoin(data.Coin)
-
- if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
- return Response{
- Code: code.CoinReserveNotSufficient,
- Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
- }
-
- commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
- }
-
- totalTxCost := big.NewInt(0).Add(data.Value, commission)
-
- if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
- }
-
- // deliver TX
-
- if !isCheck {
- rewardPull.Add(rewardPull, commissionInBaseCoin)
-
- if data.Coin != types.GetBaseCoin() {
- context.SubCoinVolume(data.Coin, commission)
- context.SubCoinReserve(data.Coin, commissionInBaseCoin)
- }
-
- context.SubBalance(sender, data.Coin, totalTxCost)
- context.AddBalance(data.To, data.Coin, data.Value)
- context.SetNonce(sender, tx.Nonce)
- }
-
- tags := common.KVPairs{
- common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeSend}},
- common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
- common.KVPair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(data.To[:]))},
- common.KVPair{Key: []byte("tx.coin"), Value: []byte(data.Coin.String())},
- }
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
+ if len(tx.Payload) > maxPayloadLength {
return Response{
- Code: code.OK,
- Tags: tags,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
-
- case TypeRedeemCheck:
-
- data := tx.GetDecodedData().(RedeemCheckData)
-
- decodedCheck, _ := check.DecodeFromBytes(data.RawCheck)
- checkSender, _ := decodedCheck.Sender()
-
- if !context.CoinExists(decodedCheck.Coin) {
- return Response{
- Code: code.CoinNotExists,
- Log: fmt.Sprintf("Coin not exists")}
- }
-
- if decodedCheck.DueBlock < uint64(currentBlock) {
- return Response{
- Code: code.CheckExpired,
- Log: fmt.Sprintf("Check expired")}
- }
-
- if context.IsCheckUsed(decodedCheck) {
- return Response{
- Code: code.CheckUsed,
- Log: fmt.Sprintf("Check already redeemed")}
- }
-
- // fixed potential problem with making too high commission for sender
- if tx.GasPrice.Cmp(big.NewInt(1)) == 1 {
- return Response{
- Code: code.TooHighGasPrice,
- Log: fmt.Sprintf("Gas price for check is limited to 1")}
- }
-
- lockPublicKey, _ := decodedCheck.LockPubKey()
-
- var senderAddressHash types.Hash
- hw := sha3.NewKeccak256()
- rlp.Encode(hw, []interface{}{
- sender,
- })
- hw.Sum(senderAddressHash[:0])
-
- pub, _ := crypto.Ecrecover(senderAddressHash[:], data.Proof[:])
-
- if bytes.Compare(lockPublicKey, pub) != 0 {
- return Response{
- Code: code.CheckInvalidLock,
- Log: fmt.Sprintf("Invalid proof")}
- }
-
- commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
- commission := big.NewInt(0).Set(commissionInBaseCoin)
-
- if decodedCheck.Coin != types.GetBaseCoin() {
- coin := context.GetStateCoin(decodedCheck.Coin)
- commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
- }
-
- totalTxCost := big.NewInt(0).Add(decodedCheck.Value, commission)
-
- if context.GetBalance(checkSender, decodedCheck.Coin).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for check issuer account: %s. Wanted %d ", checkSender.String(), totalTxCost)}
- }
-
- // deliver TX
-
- if !isCheck {
- context.UseCheck(decodedCheck)
- rewardPull.Add(rewardPull, commissionInBaseCoin)
-
- if decodedCheck.Coin != types.GetBaseCoin() {
- context.SubCoinVolume(decodedCheck.Coin, commission)
- context.SubCoinReserve(decodedCheck.Coin, commissionInBaseCoin)
- }
-
- context.SubBalance(checkSender, decodedCheck.Coin, totalTxCost)
- context.AddBalance(sender, decodedCheck.Coin, decodedCheck.Value)
- context.SetNonce(sender, tx.Nonce)
- }
-
- tags := common.KVPairs{
- common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeRedeemCheck}},
- common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(checkSender[:]))},
- common.KVPair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(sender[:]))},
- common.KVPair{Key: []byte("tx.coin"), Value: []byte(decodedCheck.Coin.String())},
- }
+ Code: code.TxPayloadTooLarge,
+ Log: "TX payload length is over 128 bytes"}
+ }
+ if len(tx.ServiceData) > maxServiceDataLength {
return Response{
- Code: code.OK,
- Tags: tags,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
-
- case TypeConvert:
-
- data := tx.GetDecodedData().(ConvertData)
-
- if data.FromCoinSymbol == data.ToCoinSymbol {
- return Response{
- Code: code.CrossConvert,
- Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin")}
- }
-
- if !context.CoinExists(data.FromCoinSymbol) {
- return Response{
- Code: code.CoinNotExists,
- Log: fmt.Sprintf("Coin not exists")}
- }
-
- if !context.CoinExists(data.ToCoinSymbol) {
- return Response{
- Code: code.CoinNotExists,
- Log: fmt.Sprintf("Coin not exists")}
- }
-
- commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
- commission := big.NewInt(0).Set(commissionInBaseCoin)
-
- if data.FromCoinSymbol != types.GetBaseCoin() {
- coin := context.GetStateCoin(data.FromCoinSymbol)
-
- if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
- return Response{
- Code: code.CoinReserveNotSufficient,
- Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
- }
-
- commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
- }
-
- totalTxCost := big.NewInt(0).Add(data.Value, commission)
-
- if context.GetBalance(sender, data.FromCoinSymbol).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
- }
-
- // deliver TX
-
- if !isCheck {
- rewardPull.Add(rewardPull, commissionInBaseCoin)
-
- context.SubBalance(sender, data.FromCoinSymbol, totalTxCost)
-
- if data.FromCoinSymbol != types.GetBaseCoin() {
- context.SubCoinVolume(data.FromCoinSymbol, commission)
- context.SubCoinReserve(data.FromCoinSymbol, commissionInBaseCoin)
- }
- }
-
- var value *big.Int
-
- if data.FromCoinSymbol == types.GetBaseCoin() {
- coin := context.GetStateCoin(data.ToCoinSymbol).Data()
-
- value = formula.CalculatePurchaseReturn(coin.Volume, coin.ReserveBalance, coin.Crr, data.Value)
-
- if !isCheck {
- context.AddCoinVolume(data.ToCoinSymbol, value)
- context.AddCoinReserve(data.ToCoinSymbol, data.Value)
- }
- } else if data.ToCoinSymbol == types.GetBaseCoin() {
- coin := context.GetStateCoin(data.FromCoinSymbol).Data()
-
- value = formula.CalculateSaleReturn(coin.Volume, coin.ReserveBalance, coin.Crr, data.Value)
-
- if !isCheck {
- context.SubCoinVolume(data.FromCoinSymbol, data.Value)
- context.SubCoinReserve(data.FromCoinSymbol, value)
- }
- } else {
- coinFrom := context.GetStateCoin(data.FromCoinSymbol).Data()
- coinTo := context.GetStateCoin(data.ToCoinSymbol).Data()
-
- basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume, coinFrom.ReserveBalance, coinFrom.Crr, data.Value)
- value = formula.CalculatePurchaseReturn(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, basecoinValue)
-
- if !isCheck {
- context.AddCoinVolume(data.ToCoinSymbol, value)
- context.SubCoinVolume(data.FromCoinSymbol, data.Value)
-
- context.AddCoinReserve(data.ToCoinSymbol, basecoinValue)
- context.SubCoinReserve(data.FromCoinSymbol, basecoinValue)
- }
- }
-
- if !isCheck {
- context.AddBalance(sender, data.ToCoinSymbol, value)
- context.SetNonce(sender, tx.Nonce)
- }
+ Code: code.TxServiceDataTooLarge,
+ Log: "TX service data length is over 128 bytes"}
+ }
- tags := common.KVPairs{
- common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeConvert}},
- common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
- common.KVPair{Key: []byte("tx.coin_to"), Value: []byte(data.ToCoinSymbol.String())},
- common.KVPair{Key: []byte("tx.coin_from"), Value: []byte(data.FromCoinSymbol.String())},
- common.KVPair{Key: []byte("tx.return"), Value: value.Bytes()},
- }
+ sender, err := tx.Sender()
+ if err != nil {
return Response{
- Code: code.OK,
- Tags: tags,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
- }
-
- case TypeCreateCoin:
-
- data := tx.GetDecodedData().(CreateCoinData)
-
- if match, _ := regexp.MatchString("^[A-Z0-9]{3,10}$", data.Symbol.String()); !match {
- return Response{
- Code: code.InvalidCoinSymbol,
- Log: fmt.Sprintf("Invalid coin symbol. Should be ^[A-Z0-9]{3,10}$")}
- }
-
- commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
- commission.Mul(commission, CommissionMultiplier)
-
- // compute additional price from letters count
- lettersCount := len(data.Symbol.String())
- var price int64 = 0
- switch lettersCount {
- case 3:
- price += 1000000 // 1mln bips
- case 4:
- price += 100000 // 100k bips
- case 5:
- price += 10000 // 10k bips
- case 6:
- price += 1000 // 1k bips
- case 7:
- price += 100 // 100 bips
- case 8:
- price += 10 // 10 bips
- }
- p := big.NewInt(10)
- p.Exp(p, big.NewInt(18), nil)
- p.Mul(p, big.NewInt(price))
- commission.Add(commission, p)
-
- totalTxCost := big.NewInt(0).Add(data.InitialReserve, commission)
-
- if context.GetBalance(sender, types.GetBaseCoin()).Cmp(totalTxCost) < 0 {
- return Response{
- Code: code.InsufficientFunds,
- Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
- }
-
- if context.CoinExists(data.Symbol) {
- return Response{
- Code: code.CoinAlreadyExists,
- Log: fmt.Sprintf("Coin already exists")}
- }
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
- if data.ConstantReserveRatio < 10 || data.ConstantReserveRatio > 100 {
+ // do not look at nonce of transaction while checking tx
+ // this will allow us to send multiple transaction from one account in one block
+ // in the future we should use "last known nonce" approach from Ethereum
+ if !isCheck {
+ if expectedNonce := context.GetNonce(sender) + 1; expectedNonce != tx.Nonce {
return Response{
- Code: code.WrongCrr,
- Log: fmt.Sprintf("Constant Reserve Ratio should be between 10 and 100")}
- }
-
- // deliver TX
-
- if !isCheck {
- rewardPull.Add(rewardPull, commission)
-
- context.SubBalance(sender, types.GetBaseCoin(), totalTxCost)
- context.CreateCoin(data.Symbol, data.Name, data.InitialAmount, data.ConstantReserveRatio, data.InitialReserve, sender)
- context.AddBalance(sender, data.Symbol, data.InitialAmount)
- context.SetNonce(sender, tx.Nonce)
- }
-
- tags := common.KVPairs{
- common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeCreateCoin}},
- common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
- common.KVPair{Key: []byte("tx.coin"), Value: []byte(data.Symbol.String())},
- }
-
- return Response{
- Code: code.OK,
- Tags: tags,
- GasUsed: tx.Gas(),
- GasWanted: tx.Gas(),
+ Code: code.WrongNonce,
+ Log: fmt.Sprintf("Unexpected nonce. Expected: %d, got %d.", expectedNonce, tx.Nonce)}
}
-
- default:
- return Response{Code: code.UnknownTransactionType}
}
- return Response{Code: code.UnknownTransactionType}
+ return tx.decodedData.Run(sender, tx, context, isCheck, rewardPull, currentBlock)
}
diff --git a/core/transaction/redeem_check.go b/core/transaction/redeem_check.go
new file mode 100644
index 000000000..26a8df569
--- /dev/null
+++ b/core/transaction/redeem_check.go
@@ -0,0 +1,159 @@
+package transaction
+
+import (
+ "bytes"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/check"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/crypto"
+ "github.com/MinterTeam/minter-go-node/crypto/sha3"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/MinterTeam/minter-go-node/rlp"
+ "github.com/tendermint/tendermint/libs/common"
+ "math/big"
+)
+
+type RedeemCheckData struct {
+ RawCheck []byte
+ Proof [65]byte
+}
+
+func (data RedeemCheckData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ RawCheck string `json:"raw_check"`
+ Proof string `json:"proof"`
+ }{
+ RawCheck: fmt.Sprintf("Mc%x", data.RawCheck),
+ Proof: fmt.Sprintf("%x", data.Proof),
+ })
+}
+
+func (data RedeemCheckData) String() string {
+ return fmt.Sprintf("REDEEM CHECK proof: %x", data.Proof)
+}
+
+func (data RedeemCheckData) Gas() int64 {
+ return commissions.SendTx
+}
+
+func (data RedeemCheckData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ decodedCheck, err := check.DecodeFromBytes(data.RawCheck)
+
+ if err != nil {
+ return Response{
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
+
+ checkSender, err := decodedCheck.Sender()
+
+ if err != nil {
+ return Response{
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
+
+ if !context.CoinExists(decodedCheck.Coin) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ if decodedCheck.DueBlock < uint64(currentBlock) {
+ return Response{
+ Code: code.CheckExpired,
+ Log: fmt.Sprintf("Check expired")}
+ }
+
+ if context.IsCheckUsed(decodedCheck) {
+ return Response{
+ Code: code.CheckUsed,
+ Log: fmt.Sprintf("Check already redeemed")}
+ }
+
+ // fixed potential problem with making too high commission for sender
+ if tx.GasPrice.Cmp(big.NewInt(1)) == 1 {
+ return Response{
+ Code: code.TooHighGasPrice,
+ Log: fmt.Sprintf("Gas price for check is limited to 1")}
+ }
+
+ lockPublicKey, err := decodedCheck.LockPubKey()
+
+ if err != nil {
+ return Response{
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
+
+ var senderAddressHash types.Hash
+ hw := sha3.NewKeccak256()
+ rlp.Encode(hw, []interface{}{
+ sender,
+ })
+ hw.Sum(senderAddressHash[:0])
+
+ pub, err := crypto.Ecrecover(senderAddressHash[:], data.Proof[:])
+
+ if err != nil {
+ return Response{
+ Code: code.DecodeError,
+ Log: err.Error()}
+ }
+
+ if bytes.Compare(lockPublicKey, pub) != 0 {
+ return Response{
+ Code: code.CheckInvalidLock,
+ Log: fmt.Sprintf("Invalid proof")}
+ }
+
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if decodedCheck.Coin != types.GetBaseCoin() {
+ coin := context.GetStateCoin(decodedCheck.Coin)
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ totalTxCost := big.NewInt(0).Add(decodedCheck.Value, commission)
+
+ if context.GetBalance(checkSender, decodedCheck.Coin).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for check issuer account: %s. Wanted %d ", checkSender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ context.UseCheck(decodedCheck)
+ rewardPull.Add(rewardPull, commissionInBaseCoin)
+
+ if decodedCheck.Coin != types.GetBaseCoin() {
+ context.SubCoinVolume(decodedCheck.Coin, commission)
+ context.SubCoinReserve(decodedCheck.Coin, commissionInBaseCoin)
+ }
+
+ context.SubBalance(checkSender, decodedCheck.Coin, totalTxCost)
+ context.AddBalance(sender, decodedCheck.Coin, decodedCheck.Value)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ tags := common.KVPairs{
+ common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeRedeemCheck}},
+ common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(checkSender[:]))},
+ common.KVPair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(sender[:]))},
+ common.KVPair{Key: []byte("tx.coin"), Value: []byte(decodedCheck.Coin.String())},
+ }
+
+ return Response{
+ Code: code.OK,
+ Tags: tags,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/sell_coin.go b/core/transaction/sell_coin.go
new file mode 100644
index 000000000..b462b4ac5
--- /dev/null
+++ b/core/transaction/sell_coin.go
@@ -0,0 +1,152 @@
+package transaction
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/tendermint/tendermint/libs/common"
+ "math/big"
+)
+
+type SellCoinData struct {
+ CoinToSell types.CoinSymbol
+ ValueToSell *big.Int
+ CoinToBuy types.CoinSymbol
+}
+
+func (data SellCoinData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ CoinToSell types.CoinSymbol `json:"coin_to_sell,string"`
+ ValueToSell string `json:"value_to_sell"`
+ CoinToBuy types.CoinSymbol `json:"coin_to_buy,string"`
+ }{
+ CoinToSell: data.CoinToSell,
+ ValueToSell: data.ValueToSell.String(),
+ CoinToBuy: data.CoinToBuy,
+ })
+}
+
+func (data SellCoinData) String() string {
+ return fmt.Sprintf("SELL COIN sell:%s %s buy:%s",
+ data.ValueToSell.String(), data.CoinToBuy.String(), data.CoinToSell.String())
+}
+
+func (data SellCoinData) Gas() int64 {
+ return commissions.ConvertTx
+}
+
+func (data SellCoinData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ if data.CoinToSell == data.CoinToBuy {
+ return Response{
+ Code: code.CrossConvert,
+ Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin")}
+ }
+
+ if !context.CoinExists(data.CoinToSell) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ if !context.CoinExists(data.CoinToBuy) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if data.CoinToSell != types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToSell)
+
+ if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
+ return Response{
+ Code: code.CoinReserveNotSufficient,
+ Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
+ }
+
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ totalTxCost := big.NewInt(0).Add(data.ValueToSell, commission)
+
+ if context.GetBalance(sender, data.CoinToSell).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commissionInBaseCoin)
+
+ context.SubBalance(sender, data.CoinToSell, totalTxCost)
+
+ if data.CoinToSell != types.GetBaseCoin() {
+ context.SubCoinVolume(data.CoinToSell, commission)
+ context.SubCoinReserve(data.CoinToSell, commissionInBaseCoin)
+ }
+ }
+
+ var value *big.Int
+
+ if data.CoinToSell == types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToBuy).Data()
+
+ value = formula.CalculatePurchaseReturn(coin.Volume, coin.ReserveBalance, coin.Crr, data.ValueToSell)
+
+ if !isCheck {
+ context.AddCoinVolume(data.CoinToBuy, value)
+ context.AddCoinReserve(data.CoinToBuy, data.ValueToSell)
+ }
+ } else if data.CoinToBuy == types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.CoinToSell).Data()
+
+ value = formula.CalculateSaleReturn(coin.Volume, coin.ReserveBalance, coin.Crr, data.ValueToSell)
+
+ if !isCheck {
+ context.SubCoinVolume(data.CoinToSell, data.ValueToSell)
+ context.SubCoinReserve(data.CoinToSell, value)
+ }
+ } else {
+ coinFrom := context.GetStateCoin(data.CoinToSell).Data()
+ coinTo := context.GetStateCoin(data.CoinToBuy).Data()
+
+ basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume, coinFrom.ReserveBalance, coinFrom.Crr, data.ValueToSell)
+ value = formula.CalculatePurchaseReturn(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, basecoinValue)
+
+ if !isCheck {
+ context.AddCoinVolume(data.CoinToBuy, value)
+ context.SubCoinVolume(data.CoinToSell, data.ValueToSell)
+
+ context.AddCoinReserve(data.CoinToBuy, basecoinValue)
+ context.SubCoinReserve(data.CoinToSell, basecoinValue)
+ }
+ }
+
+ if !isCheck {
+ context.AddBalance(sender, data.CoinToBuy, value)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ tags := common.KVPairs{
+ common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeSellCoin}},
+ common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
+ common.KVPair{Key: []byte("tx.coin_to_buy"), Value: []byte(data.CoinToBuy.String())},
+ common.KVPair{Key: []byte("tx.coin_to_sell"), Value: []byte(data.CoinToSell.String())},
+ common.KVPair{Key: []byte("tx.return"), Value: value.Bytes()},
+ }
+
+ return Response{
+ Code: code.OK,
+ Tags: tags,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/send.go b/core/transaction/send.go
new file mode 100644
index 000000000..26d786a02
--- /dev/null
+++ b/core/transaction/send.go
@@ -0,0 +1,100 @@
+package transaction
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/formula"
+ "github.com/tendermint/tendermint/libs/common"
+ "math/big"
+)
+
+type SendData struct {
+ Coin types.CoinSymbol
+ To types.Address
+ Value *big.Int
+}
+
+func (data SendData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ Coin types.CoinSymbol `json:"coin,string"`
+ To types.Address `json:"to"`
+ Value string `json:"value"`
+ }{
+ Coin: data.Coin,
+ To: data.To,
+ Value: data.Value.String(),
+ })
+}
+
+func (data SendData) String() string {
+ return fmt.Sprintf("SEND to:%s coin:%s value:%s",
+ data.To.String(), data.Coin.String(), data.Value.String())
+}
+
+func (data SendData) Gas() int64 {
+ return commissions.SendTx
+}
+
+func (data SendData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ if !context.CoinExists(data.Coin) {
+ return Response{
+ Code: code.CoinNotExists,
+ Log: fmt.Sprintf("Coin not exists")}
+ }
+
+ commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier)
+ commission := big.NewInt(0).Set(commissionInBaseCoin)
+
+ if data.Coin != types.GetBaseCoin() {
+ coin := context.GetStateCoin(data.Coin)
+
+ if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 {
+ return Response{
+ Code: code.CoinReserveNotSufficient,
+ Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.ReserveBalance().String(), commissionInBaseCoin.String())}
+ }
+
+ commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin)
+ }
+
+ totalTxCost := big.NewInt(0).Add(data.Value, commission)
+
+ if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commissionInBaseCoin)
+
+ if data.Coin != types.GetBaseCoin() {
+ context.SubCoinVolume(data.Coin, commission)
+ context.SubCoinReserve(data.Coin, commissionInBaseCoin)
+ }
+
+ context.SubBalance(sender, data.Coin, totalTxCost)
+ context.AddBalance(data.To, data.Coin, data.Value)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ tags := common.KVPairs{
+ common.KVPair{Key: []byte("tx.type"), Value: []byte{TypeSend}},
+ common.KVPair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))},
+ common.KVPair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(data.To[:]))},
+ common.KVPair{Key: []byte("tx.coin"), Value: []byte(data.Coin.String())},
+ }
+
+ return Response{
+ Code: code.OK,
+ Tags: tags,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/switch_candidate_status.go b/core/transaction/switch_candidate_status.go
new file mode 100644
index 000000000..e6b07923d
--- /dev/null
+++ b/core/transaction/switch_candidate_status.go
@@ -0,0 +1,132 @@
+package transaction
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "math/big"
+)
+
+type SetCandidateOnData struct {
+ PubKey []byte
+}
+
+func (data SetCandidateOnData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ PubKey string `json:"pub_key"`
+ }{
+ PubKey: fmt.Sprintf("Mp%x", data.PubKey),
+ })
+}
+
+func (data SetCandidateOnData) String() string {
+ return fmt.Sprintf("SET CANDIDATE ONLINE pubkey: %x",
+ data.PubKey)
+}
+
+func (data SetCandidateOnData) Gas() int64 {
+ return commissions.ToggleCandidateStatus
+}
+
+func (data SetCandidateOnData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commission.Mul(commission, CommissionMultiplier)
+
+ if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
+ }
+
+ if !context.CandidateExists(data.PubKey) {
+ return Response{
+ Code: code.CandidateNotFound,
+ Log: fmt.Sprintf("Candidate with such public key (%x) not found", data.PubKey)}
+ }
+
+ candidate := context.GetStateCandidate(data.PubKey)
+
+ if bytes.Compare(candidate.CandidateAddress.Bytes(), sender.Bytes()) != 0 {
+ return Response{
+ Code: code.IsNotOwnerOfCandidate,
+ Log: fmt.Sprintf("Sender is not an owner of a candidate")}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, types.GetBaseCoin(), commission)
+ context.SetCandidateOnline(data.PubKey)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ return Response{
+ Code: code.OK,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
+
+type SetCandidateOffData struct {
+ PubKey []byte
+}
+
+func (data SetCandidateOffData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ PubKey string `json:"pubkey"`
+ }{
+ PubKey: fmt.Sprintf("Mp%x", data.PubKey),
+ })
+}
+
+func (data SetCandidateOffData) String() string {
+ return fmt.Sprintf("SET CANDIDATE OFFLINE pubkey: %x",
+ data.PubKey)
+}
+
+func (data SetCandidateOffData) Gas() int64 {
+ return commissions.ToggleCandidateStatus
+}
+
+func (data SetCandidateOffData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commission.Mul(commission, CommissionMultiplier)
+
+ if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
+ }
+
+ if !context.CandidateExists(data.PubKey) {
+ return Response{
+ Code: code.CandidateNotFound,
+ Log: fmt.Sprintf("Candidate with such public key not found")}
+ }
+
+ candidate := context.GetStateCandidate(data.PubKey)
+
+ if bytes.Compare(candidate.CandidateAddress.Bytes(), sender.Bytes()) != 0 {
+ return Response{
+ Code: code.IsNotOwnerOfCandidate,
+ Log: fmt.Sprintf("Sender is not an owner of a candidate")}
+ }
+
+ if !isCheck {
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, types.GetBaseCoin(), commission)
+ context.SetCandidateOffline(data.PubKey)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ return Response{
+ Code: code.OK,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/transaction/transaction.go b/core/transaction/transaction.go
index 10fb8616e..580b8dbe2 100644
--- a/core/transaction/transaction.go
+++ b/core/transaction/transaction.go
@@ -3,14 +3,13 @@ package transaction
import (
"bytes"
"crypto/ecdsa"
- "encoding/json"
"errors"
"fmt"
"github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
"github.com/MinterTeam/minter-go-node/core/types"
"github.com/MinterTeam/minter-go-node/crypto"
"github.com/MinterTeam/minter-go-node/crypto/sha3"
- "github.com/MinterTeam/minter-go-node/hexutil"
"github.com/MinterTeam/minter-go-node/rlp"
"math/big"
)
@@ -21,17 +20,17 @@ var (
const (
TypeSend byte = 0x01
- TypeConvert byte = 0x02
- TypeCreateCoin byte = 0x03
- TypeDeclareCandidacy byte = 0x04
- TypeDelegate byte = 0x05
- TypeUnbond byte = 0x06
- TypeRedeemCheck byte = 0x07
- TypeSetCandidateOnline byte = 0x08
- TypeSetCandidateOffline byte = 0x09
+ TypeSellCoin byte = 0x02
+ TypeBuyCoin byte = 0x03
+ TypeCreateCoin byte = 0x04
+ TypeDeclareCandidacy byte = 0x05
+ TypeDelegate byte = 0x06
+ TypeUnbond byte = 0x07
+ TypeRedeemCheck byte = 0x08
+ TypeSetCandidateOnline byte = 0x09
+ TypeSetCandidateOffline byte = 0x0A
)
-// TODO: refactor, get rid of switch cases
type Transaction struct {
Nonce uint64
GasPrice *big.Int
@@ -50,261 +49,28 @@ type RawData []byte
type Data interface {
MarshalJSON() ([]byte, error)
-}
-
-type SendData struct {
- Coin types.CoinSymbol
- To types.Address
- Value *big.Int
-}
-
-func (s SendData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- Coin types.CoinSymbol `json:"coin,string"`
- To types.Address `json:"to"`
- Value string `json:"value"`
- }{
- Coin: s.Coin,
- To: s.To,
- Value: s.Value.String(),
- })
-}
-
-type SetCandidateOnData struct {
- PubKey []byte
-}
-
-func (s SetCandidateOnData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- PubKey []byte `json:"pubkey"`
- }{})
-}
-
-type SetCandidateOffData struct {
- PubKey []byte
-}
-
-func (s SetCandidateOffData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- PubKey []byte `json:"pubkey"`
- }{})
-}
-
-type ConvertData struct {
- FromCoinSymbol types.CoinSymbol
- ToCoinSymbol types.CoinSymbol
- Value *big.Int
-}
-
-func (s ConvertData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- FromCoin types.CoinSymbol `json:"from_coin,string"`
- ToCoin types.CoinSymbol `json:"to_coin,string"`
- Value string `json:"value"`
- }{
- FromCoin: s.FromCoinSymbol,
- ToCoin: s.ToCoinSymbol,
- Value: s.Value.String(),
- })
-}
-
-type CreateCoinData struct {
- Name string
- Symbol types.CoinSymbol
- InitialAmount *big.Int
- InitialReserve *big.Int
- ConstantReserveRatio uint
-}
-
-func (s CreateCoinData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- Name string `json:"name"`
- Symbol types.CoinSymbol `json:"coin_symbol"`
- InitialAmount string `json:"initial_amount"`
- InitialReserve string `json:"initial_reserve"`
- ConstantReserveRatio uint `json:"constant_reserve_ratio"`
- }{
- Name: s.Name,
- Symbol: s.Symbol,
- InitialAmount: s.InitialAmount.String(),
- InitialReserve: s.InitialReserve.String(),
- ConstantReserveRatio: s.ConstantReserveRatio,
- })
-}
-
-type DeclareCandidacyData struct {
- Address types.Address
- PubKey []byte
- Commission uint
- Coin types.CoinSymbol
- Stake *big.Int
-}
-
-func (s DeclareCandidacyData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- Address types.Address
- PubKey string
- Commission uint
- Coin types.CoinSymbol
- Stake string
- }{
- Address: s.Address,
- PubKey: fmt.Sprintf("Mp%x", s.PubKey),
- Commission: s.Commission,
- Coin: s.Coin,
- Stake: s.Stake.String(),
- })
-}
-
-type DelegateData struct {
- PubKey []byte
- Coin types.CoinSymbol
- Stake *big.Int
-}
-
-func (s DelegateData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- PubKey string
- Coin types.CoinSymbol
- Stake string
- }{
- PubKey: fmt.Sprintf("Mp%x", s.PubKey),
- Coin: s.Coin,
- Stake: s.Stake.String(),
- })
-}
-
-type RedeemCheckData struct {
- RawCheck []byte
- Proof [65]byte
-}
-
-func (s RedeemCheckData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- RawCheck string
- Proof string
- }{
- RawCheck: fmt.Sprintf("Mc%x", s.RawCheck),
- Proof: fmt.Sprintf("%x", s.Proof),
- })
-}
-
-type UnbondData struct {
- PubKey []byte
- Coin types.CoinSymbol
- Value *big.Int
-}
-
-func (s UnbondData) MarshalJSON() ([]byte, error) {
- return json.Marshal(struct {
- PubKey string
- Coin types.CoinSymbol
- Value string
- }{
- PubKey: fmt.Sprintf("Mp%x", s.PubKey),
- Coin: s.Coin,
- Value: s.Value.String(),
- })
+ String() string
+ Gas() int64
+ Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response
}
func (tx *Transaction) Serialize() ([]byte, error) {
-
- buf, err := rlp.EncodeToBytes(tx)
-
- return buf, err
+ return rlp.EncodeToBytes(tx)
}
func (tx *Transaction) Gas() int64 {
+ return tx.decodedData.Gas() + tx.payloadGas()
+}
- gas := int64(0)
-
- switch tx.Type {
- case TypeSend:
- gas = commissions.SendTx
- case TypeConvert:
- gas = commissions.ConvertTx
- case TypeCreateCoin:
- gas = commissions.CreateTx
- case TypeDeclareCandidacy:
- gas = commissions.DeclareCandidacyTx
- case TypeDelegate:
- gas = commissions.DelegateTx
- case TypeUnbond:
- gas = commissions.UnboundTx
- case TypeRedeemCheck:
- gas = commissions.RedeemCheckTx
- case TypeSetCandidateOnline:
- gas = commissions.ToggleCandidateStatus
- case TypeSetCandidateOffline:
- gas = commissions.ToggleCandidateStatus
- }
-
- gas = gas + int64(len(tx.Payload)+len(tx.ServiceData))*commissions.PayloadByte
-
- return gas
+func (tx *Transaction) payloadGas() int64 {
+ return int64(len(tx.Payload)+len(tx.ServiceData)) * commissions.PayloadByte
}
func (tx *Transaction) String() string {
sender, _ := tx.Sender()
- switch tx.Type {
- case TypeSend:
- {
- txData := tx.decodedData.(SendData)
- return fmt.Sprintf("SEND TX nonce:%d from:%s to:%s coin:%s value:%s payload: %s",
- tx.Nonce, sender.String(), txData.To.String(), txData.Coin.String(), txData.Value.String(), tx.Payload)
- }
- case TypeConvert:
- {
- txData := tx.decodedData.(ConvertData)
- return fmt.Sprintf("CONVERT TX nonce:%d from:%s to:%s coin:%s value:%s payload: %s",
- tx.Nonce, sender.String(), txData.FromCoinSymbol.String(), txData.ToCoinSymbol.String(), txData.Value.String(), tx.Payload)
- }
- case TypeCreateCoin:
- {
- txData := tx.decodedData.(CreateCoinData)
- return fmt.Sprintf("CREATE COIN TX nonce:%d from:%s symbol:%s reserve:%s amount:%s crr:%d payload: %s",
- tx.Nonce, sender.String(), txData.Symbol.String(), txData.InitialReserve, txData.InitialAmount, txData.ConstantReserveRatio, tx.Payload)
- }
- case TypeDeclareCandidacy:
- {
- txData := tx.decodedData.(DeclareCandidacyData)
- return fmt.Sprintf("DECLARE CANDIDACY TX nonce:%d address:%s pubkey:%s commission: %d payload: %s",
- tx.Nonce, txData.Address.String(), hexutil.Encode(txData.PubKey[:]), txData.Commission, tx.Payload)
- }
- case TypeDelegate:
- {
- txData := tx.decodedData.(DelegateData)
- return fmt.Sprintf("DELEGATE TX nonce:%d pubkey:%s payload: %s",
- tx.Nonce, hexutil.Encode(txData.PubKey[:]), tx.Payload)
- }
- case TypeUnbond:
- {
- txData := tx.decodedData.(UnbondData)
- return fmt.Sprintf("UNBOUND TX nonce:%d pubkey:%s payload: %s",
- tx.Nonce, hexutil.Encode(txData.PubKey[:]), tx.Payload)
- }
- case TypeRedeemCheck:
- {
- txData := tx.decodedData.(RedeemCheckData)
- return fmt.Sprintf("REDEEM CHECK TX nonce:%d proof: %x",
- tx.Nonce, txData.Proof)
- }
- case TypeSetCandidateOffline:
- {
- txData := tx.decodedData.(SetCandidateOffData)
- return fmt.Sprintf("SET CANDIDATE OFFLINE TX nonce:%d, pubkey: %x",
- tx.Nonce, txData.PubKey)
- }
- case TypeSetCandidateOnline:
- {
- txData := tx.decodedData.(SetCandidateOnData)
- return fmt.Sprintf("SET CANDIDATE ONLINE TX nonce:%d, pubkey: %x",
- tx.Nonce, txData.PubKey)
- }
- }
-
- return "err"
+ return fmt.Sprintf("TX nonce:%d from:%s payload:%s data:%s",
+ tx.Nonce, sender.String(), tx.Payload, tx.decodedData.String())
}
func (tx *Transaction) Sign(prv *ecdsa.PrivateKey) error {
@@ -327,7 +93,7 @@ func (tx *Transaction) SetSignature(sig []byte) {
}
func (tx *Transaction) Sender() (types.Address, error) {
- return recoverPlain(tx.Hash(), tx.R, tx.S, tx.V, true)
+ return recoverPlain(tx.Hash(), tx.R, tx.S, tx.V)
}
func (tx *Transaction) Hash() types.Hash {
@@ -349,12 +115,12 @@ func (tx *Transaction) GetDecodedData() Data {
return tx.decodedData
}
-func recoverPlain(sighash types.Hash, R, S, Vb *big.Int, homestead bool) (types.Address, error) {
+func recoverPlain(sighash types.Hash, R, S, Vb *big.Int) (types.Address, error) {
if Vb.BitLen() > 8 {
return types.Address{}, ErrInvalidSig
}
V := byte(Vb.Uint64() - 27)
- if !crypto.ValidateSignatureValues(V, R, S, homestead) {
+ if !crypto.ValidateSignatureValues(V, R, S) {
return types.Address{}, ErrInvalidSig
}
// encode the snature in uncompressed format
@@ -386,72 +152,121 @@ func rlpHash(x interface{}) (h types.Hash) {
func DecodeFromBytes(buf []byte) (*Transaction, error) {
var tx Transaction
- rlp.Decode(bytes.NewReader(buf), &tx)
+ err := rlp.Decode(bytes.NewReader(buf), &tx)
+
+ if err != nil {
+ return nil, err
+ }
switch tx.Type {
case TypeSend:
{
data := SendData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.Value == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeRedeemCheck:
{
data := RedeemCheckData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
+ tx.SetDecodedData(data)
+
+ if data.RawCheck == nil {
+ return nil, errors.New("incorrect tx data")
+ }
+ }
+ case TypeSellCoin:
+ {
+ data := SellCoinData{}
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.ValueToSell == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
- case TypeConvert:
+ case TypeBuyCoin:
{
- data := ConvertData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ data := BuyCoinData{}
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.ValueToBuy == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeCreateCoin:
{
data := CreateCoinData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
if data.InitialReserve == nil || data.InitialAmount == nil {
- fmt.Printf("%s\n", tx.String())
return nil, errors.New("incorrect tx data")
}
}
case TypeDeclareCandidacy:
{
data := DeclareCandidacyData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.PubKey == nil || data.Stake == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeDelegate:
{
data := DelegateData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.PubKey == nil || data.Stake == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeSetCandidateOnline:
{
data := SetCandidateOnData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.PubKey == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeSetCandidateOffline:
{
data := SetCandidateOffData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.PubKey == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
case TypeUnbond:
{
data := UnbondData{}
- rlp.Decode(bytes.NewReader(tx.Data), &data)
+ err = rlp.Decode(bytes.NewReader(tx.Data), &data)
tx.SetDecodedData(data)
+
+ if data.PubKey == nil || data.Value == nil {
+ return nil, errors.New("incorrect tx data")
+ }
}
default:
return nil, errors.New("incorrect tx data")
}
+ if err != nil {
+ return nil, err
+ }
+
if tx.S == nil || tx.R == nil || tx.V == nil {
return nil, errors.New("incorrect tx signature")
}
diff --git a/core/transaction/unbond.go b/core/transaction/unbond.go
new file mode 100644
index 000000000..41d52a4d3
--- /dev/null
+++ b/core/transaction/unbond.go
@@ -0,0 +1,92 @@
+package transaction
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/MinterTeam/minter-go-node/core/code"
+ "github.com/MinterTeam/minter-go-node/core/commissions"
+ "github.com/MinterTeam/minter-go-node/core/state"
+ "github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/hexutil"
+ "math/big"
+)
+
+const unbondPeriod = 518400
+
+type UnbondData struct {
+ PubKey []byte
+ Coin types.CoinSymbol
+ Value *big.Int
+}
+
+func (data UnbondData) MarshalJSON() ([]byte, error) {
+ return json.Marshal(struct {
+ PubKey string `json:"pub_key"`
+ Coin types.CoinSymbol `json:"coin"`
+ Value string `json:"value"`
+ }{
+ PubKey: fmt.Sprintf("Mp%x", data.PubKey),
+ Coin: data.Coin,
+ Value: data.Value.String(),
+ })
+}
+
+func (data UnbondData) String() string {
+ return fmt.Sprintf("UNBOND pubkey:%s",
+ hexutil.Encode(data.PubKey[:]))
+}
+
+func (data UnbondData) Gas() int64 {
+ return commissions.UnbondTx
+}
+
+func (data UnbondData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response {
+ commission := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas()))
+ commission.Mul(commission, CommissionMultiplier)
+
+ if context.GetBalance(sender, types.GetBaseCoin()).Cmp(commission) < 0 {
+ return Response{
+ Code: code.InsufficientFunds,
+ Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)}
+ }
+
+ if !context.CandidateExists(data.PubKey) {
+ return Response{
+ Code: code.CandidateNotFound,
+ Log: fmt.Sprintf("Candidate with such public key not found")}
+ }
+
+ candidate := context.GetStateCandidate(data.PubKey)
+
+ stake := candidate.GetStakeOfAddress(sender, data.Coin)
+
+ if stake == nil {
+ return Response{
+ Code: code.StakeNotFound,
+ Log: fmt.Sprintf("Stake of current user not found")}
+ }
+
+ if stake.Value.Cmp(data.Value) < 0 {
+ return Response{
+ Code: code.InsufficientStake,
+ Log: fmt.Sprintf("Insufficient stake for sender account")}
+ }
+
+ if !isCheck {
+ // now + 31 days
+ unbondAtBlock := currentBlock + unbondPeriod
+
+ rewardPull.Add(rewardPull, commission)
+
+ context.SubBalance(sender, types.GetBaseCoin(), commission)
+ context.SubStake(sender, data.PubKey, data.Coin, data.Value)
+ context.GetOrNewStateFrozenFunds(unbondAtBlock).AddFund(sender, data.PubKey, data.Coin, data.Value)
+ context.SetNonce(sender, tx.Nonce)
+ }
+
+ return Response{
+ Code: code.OK,
+ GasUsed: tx.Gas(),
+ GasWanted: tx.Gas(),
+ }
+}
diff --git a/core/types/bytes_test.go b/core/types/bytes_test.go
index aaf1f00f5..4f4187875 100644
--- a/core/types/bytes_test.go
+++ b/core/types/bytes_test.go
@@ -59,7 +59,7 @@ func (s *BytesSuite) TestRightPadBytes(c *checker.C) {
func TestFromHex(t *testing.T) {
input := "Mx01"
expected := []byte{1}
- result := FromHex(input)
+ result := FromHex(input, "Mx")
if !bytes.Equal(expected, result) {
t.Errorf("Expected %x got %x", expected, result)
}
@@ -89,7 +89,7 @@ func TestIsHex(t *testing.T) {
func TestFromHexOddLength(t *testing.T) {
input := "Mx1"
expected := []byte{1}
- result := FromHex(input)
+ result := FromHex(input, "Mx")
if !bytes.Equal(expected, result) {
t.Errorf("Expected %x got %x", expected, result)
}
@@ -98,7 +98,7 @@ func TestFromHexOddLength(t *testing.T) {
func TestNoPrefixShortHexOddLength(t *testing.T) {
input := "1"
expected := []byte{1}
- result := FromHex(input)
+ result := FromHex(input, "Mx")
if !bytes.Equal(expected, result) {
t.Errorf("Expected %x got %x", expected, result)
}
diff --git a/crypto/crypto.go b/crypto/crypto.go
index e7d6f6b21..b41d468f1 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -181,13 +181,13 @@ func GenerateKey() (*ecdsa.PrivateKey, error) {
// ValidateSignatureValues verifies whether the signature values are valid with
// the given chain rules. The v value is assumed to be either 0 or 1.
-func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool {
+func ValidateSignatureValues(v byte, r, s *big.Int) bool {
if r.Cmp(types.Big1) < 0 || s.Cmp(types.Big1) < 0 {
return false
}
// reject upper range of s values (ECDSA malleability)
// see discussion in secp256k1/libsecp256k1/include/secp256k1.h
- if homestead && s.Cmp(secp256k1halfN) > 0 {
+ if s.Cmp(secp256k1halfN) > 0 {
return false
}
// Frontier: allow s to be in full N range
diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go
index 16f20eda8..9a3987133 100644
--- a/crypto/crypto_test.go
+++ b/crypto/crypto_test.go
@@ -140,7 +140,7 @@ func TestNewContractAddress(t *testing.T) {
}
func TestLoadECDSAFile(t *testing.T) {
- keyBytes := types.FromHex(testPrivHex)
+ keyBytes := types.FromHex(testPrivHex, "")
fileName0 := "test_key0"
fileName1 := "test_key1"
checkKey := func(k *ecdsa.PrivateKey) {
@@ -242,7 +242,7 @@ func TestPythonIntegration(t *testing.T) {
msg0 := Keccak256([]byte("foo"))
sig0, _ := Sign(msg0, k0)
- msg1 := types.FromHex("00000000000000000000000000000000")
+ msg1 := types.FromHex("00000000000000000000000000000000", "")
sig1, _ := Sign(msg0, k0)
t.Logf("msg: %x, privkey: %s sig: %x\n", msg0, kh, sig0)
diff --git a/crypto/randentropy/rand_entropy.go b/crypto/randentropy/rand_entropy.go
deleted file mode 100644
index 539d3ac89..000000000
--- a/crypto/randentropy/rand_entropy.go
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package randentropy
-
-import (
- crand "crypto/rand"
- "io"
-)
-
-var Reader io.Reader = &randEntropy{}
-
-type randEntropy struct {
-}
-
-func (*randEntropy) Read(bytes []byte) (n int, err error) {
- readBytes := GetEntropyCSPRNG(len(bytes))
- copy(bytes, readBytes)
- return len(bytes), nil
-}
-
-func GetEntropyCSPRNG(n int) []byte {
- mainBuff := make([]byte, n)
- _, err := io.ReadFull(crand.Reader, mainBuff)
- if err != nil {
- panic("reading from crypto/rand failed: " + err.Error())
- }
- return mainBuff
-}
diff --git a/docker-compose.yml b/docker-compose.yml
index 9e26314bc..179da8cda 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,7 +1,7 @@
version: "3.4"
services:
minter:
- image: minterteam/minter:0.0.5
+ image: minterteam/minter:0.0.6
command: --tendermint_addr=tcp://tendermint:46657
volumes:
- ~/.minter:/minter
@@ -15,7 +15,7 @@ services:
retries: 3
start_period: 30s
tendermint:
- image: tendermint/tendermint:0.22.0
+ image: tendermint/tendermint:0.22.4
command: node --proxy_app=tcp://minter:46658
volumes:
- ~/.tendermint:/tendermint
diff --git a/docs/api.rst b/docs/api.rst
index bac56ce00..7edf453c6 100755
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -1,6 +1,12 @@
Minter Node API
===============
+Minter Node API is based on JSON format. JSON is a lightweight data-interchange format.
+It can represent numbers, strings, ordered sequences of values, and collections of name/value pairs.
+
+If request is successful, Minter Node API will respond with ``result`` key and code equal to zero. Otherwise, it will
+respond with non-zero code and key ``log`` with error description.
+
Status
^^^^^^
@@ -142,6 +148,8 @@ Returns balance of an account.
}
}
+**Result**: Map of balances. CoinSymbol => Balance (in pips).
+
Transaction count
^^^^^^^^^^^^^^^^^
@@ -159,6 +167,8 @@ transaction.
"result": 3
}
+**Result**: Count of transactions sent from given account.
+
Send transaction
^^^^^^^^^^^^^^^^
@@ -175,9 +185,13 @@ Sends transaction to the Minter Network.
"result": "Mtfd5c3ecad1e8333564cf6e3f968578b9db5acea3"
}
+**Result**: Transaction hash.
+
Transaction
^^^^^^^^^^^
+*In development*
+
.. code-block:: bash
curl -s 'localhost:8841/api/transaction/{hash}'
@@ -186,7 +200,7 @@ Transaction
{
"code": 0,
- "result": ...
+ "result": {}
}
Block
@@ -266,8 +280,62 @@ Returns information about coin.
"symbol":"BLTCOIN",
"volume":"3162375676992609621",
"crr":10,
- "reserve_coin":"MNT",
"reserve_balance":"100030999965000000000000",
"creator":"Mxc07ec7cdcae90dea3999558f022aeb25dabbeea2"
}
- }
\ No newline at end of file
+ }
+
+**Result**:
+ - **Coin name** - Name of a coin. Arbitrary string.
+ - **Coin symbol** - Short symbol of a coin. Coin symbol is unique, alphabetic, uppercase, 3 to 10 letters length.
+ - **Volume** - Amount of coins exists in network.
+ - **Reserve balance** - Amount of BIP/MNT in coin reserve.
+ - **Constant Reserve Ratio (CRR)** - uint, from 10 to 100.
+ - **Creator** - Address of coin creator account.
+
+Estimate sell coin
+^^^^^^^^^^^^^^^^^^
+
+Return estimate of sell coin transaction
+
+.. code-block:: bash
+
+ curl -s 'localhost:8841/api/estimateCoinSell?coin_to_sell=MNT&value_to_sell=1000000000000000000&coin_to_buy=BLTCOIN'
+
+Request params:
+ - **coin_to_sell** – coin to give
+ - **value_to_sell** – amount to give (in pips)
+ - **coin_to_buy** - coin to get
+
+.. code-block:: json
+
+ {
+ "code": 0,
+ "result": "29808848728151191"
+ }
+
+**Result**: Amount of "to_coin" user should get.
+
+
+Estimate buy coin
+^^^^^^^^^^^^^^^^^
+
+Return estimate of buy coin transaction
+
+.. code-block:: bash
+
+ curl -s 'localhost:8841/api/estimateCoinBuy?coin_to_sell=MNT&value_to_buy=1000000000000000000&coin_to_buy=BLTCOIN'
+
+Request params:
+ - **coin_to_sell** – coin to give
+ - **value_to_buy** – amount to get (in pips)
+ - **coin_to_buy** - coin to get
+
+.. code-block:: json
+
+ {
+ "code": 0,
+ "result": "29808848728151191"
+ }
+
+**Result**: Amount of "to_coin" user should give.
diff --git a/docs/assets/redeem-check.png b/docs/assets/redeem-check.png
new file mode 100644
index 000000000..cf03983f6
Binary files /dev/null and b/docs/assets/redeem-check.png differ
diff --git a/docs/checks.rst b/docs/checks.rst
index fc30e7508..5923533f5 100755
--- a/docs/checks.rst
+++ b/docs/checks.rst
@@ -1,3 +1,65 @@
Minter Check
============
+Minter Check is like an ordinary bank check. Each user of network can issue check with any amount of coins
+and pass it to another person. Receiver will be able to cash a check from arbitrary account.
+
+Introduction
+^^^^^^^^^^^^
+
+Checks are prefixed with "Mc". Here is example of a Minter Check:
+
+.. code-block:: text
+
+ Mcf89b01830f423f8a4d4e5400000000000000843b9aca00b8419b3beac2c6ad88a8bd54d2
+ 4912754bb820e58345731cb1b9bc0885ee74f9e50a58a80aa990a29c98b05541b266af99d3
+ 825bb1e5ed4e540c6e2f7c9b40af9ecc011ca0387fd67ec41be0f1cf92c7d0181368b4c67a
+ b07df2d2384192520d74ff77ace6a04ba0e7ad7b34c64223fe59584bc464d53fcdc7091faa
+ ee5df0451254062cfb37
+
+Each Minter Check has:
+ - **Nonce** - unique "id" of the check.
+ - **Coin Symbol** - symbol of coin.
+ - **Value** - amount of coins.
+ - **Due Block** - defines last block height in which the check can be used.
+ - **Lock** - secret to prevent hijacking.
+ - **Signature** - signature of issuer.
+
+Check hijacking protection
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Minter Checks are issued offline and do not exist in blockchain before "cashing".
+So we decided to use special passphrase to protect checks from hijacking by another person in the moment of activation.
+Hash of this passphrase is used as private key in ECDSA to prove that sender is the one who owns the check.
+
+*TODO: describe algorithm*
+
+How to issue a Minter Check
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For issuing Minter Check you can use our `tool `__.
+
+You will need to fill a form:
+ - **Nonce** - unique "id" of the check.
+ - **Coin Symbol** - symbol of coin.
+ - **Value** - amount of coins.
+ - **Pass phrase** - secret phrase which you will pass to receiver of the check.
+ - **Private key** - private key of an account with funds to send.
+
+How to cash a Minter Check
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To redeem a check user should have:
+ - Check itself
+ - Secret passphrase
+
+.. figure:: assets/redeem-check.png
+ :width: 300px
+
+After redeeming balance of user will increased instantly.
+
+Commission
+^^^^^^^^^^
+
+There is no commission for issuing a check because it done offline. In the moment of
+cashing issuer will pay standard "send" commission.
diff --git a/docs/commissions.rst b/docs/commissions.rst
index c32d2e1eb..9556bdd6c 100755
--- a/docs/commissions.rst
+++ b/docs/commissions.rst
@@ -3,34 +3,38 @@ Commissions
For each transaction sender should pay fee. Fees are measured in "units".
-1 unit = 10^8 pip = 0.00000001 bip.
+1 unit = 10^15 pip = 0.001 bip.
Standard commissions
^^^^^^^^^^^^^^^^^^^^
Here is a list of current fees:
-+----------------------------------+--------------+
-| Type | Fee |
-+==================================+==============+
-| **TypeSend** | 1000 units |
-+----------------------------------+--------------+
-| **TypeConvert** | 10000 units |
-+----------------------------------+--------------+
-| **TypeCreateCoin** | 100000 units |
-+----------------------------------+--------------+
-| **TypeDeclareCandidacy** | 100000 units |
-+----------------------------------+--------------+
-| **TypeDelegate** | 10000 units |
-+----------------------------------+--------------+
-| **TypeUnbond** | 10000 units |
-+----------------------------------+--------------+
-| **TypeRedeemCheck** | 1000 units |
-+----------------------------------+--------------+
-| **TypeSetCandidateOnline** | 500 units |
-+----------------------------------+--------------+
-| **TypeSetCandidateOffline** | 1000 units |
-+----------------------------------+--------------+
++----------------------------------+---------------------+
+| Type | Fee |
++==================================+=====================+
+| **TypeSend** | 10 units |
++----------------------------------+---------------------+
+| **TypeSellCoin** | 100 units |
++----------------------------------+---------------------+
+| **TypeBuyCoin** | 100 units |
++----------------------------------+---------------------+
+| **TypeCreateCoin** | 1000 units |
++----------------------------------+---------------------+
+| **TypeDeclareCandidacy** | 10000 units |
++----------------------------------+---------------------+
+| **TypeDelegate** | 100 units |
++----------------------------------+---------------------+
+| **TypeUnbond** | 100 units |
++----------------------------------+---------------------+
+| **TypeRedeemCheck** | 10 units |
++----------------------------------+---------------------+
+| **TypeSetCandidateOnline** | 100 units |
++----------------------------------+---------------------+
+| **TypeSetCandidateOffline** | 100 units |
++----------------------------------+---------------------+
+
+Also sender should pay extra 2 units per byte in Payload and Service Data fields.
Special fees
^^^^^^^^^^^^
diff --git a/docs/install.rst b/docs/install.rst
index 6ad89c740..cecb2bc06 100755
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -49,7 +49,7 @@ From Source
You'll need ``go`` `installed `__ and the required
`environment variables set `__
-Install Tendermint 0.22.0
+Install Tendermint 0.22.4
^^^^^^^^^^^^^^^^^^^^^^^^^
`Read official instructions `__
diff --git a/docs/transactions.rst b/docs/transactions.rst
index 4e5455e18..9fca7b0c8 100755
--- a/docs/transactions.rst
+++ b/docs/transactions.rst
@@ -49,21 +49,23 @@ Type of transaction is determined by a single byte.
+==================================+=========+
| **TypeSend** | 0x01 |
+----------------------------------+---------+
-| **TypeConvert** | 0x02 |
+| **TypeSellCoin** | 0x02 |
+----------------------------------+---------+
-| **TypeCreateCoin** | 0x03 |
+| **TypeBuyCoin** | 0x03 |
+----------------------------------+---------+
-| **TypeDeclareCandidacy** | 0x04 |
+| **TypeCreateCoin** | 0x04 |
+----------------------------------+---------+
-| **TypeDelegate** | 0x05 |
+| **TypeDeclareCandidacy** | 0x05 |
+----------------------------------+---------+
-| **TypeUnbond** | 0x06 |
+| **TypeDelegate** | 0x06 |
+----------------------------------+---------+
-| **TypeRedeemCheck** | 0x07 |
+| **TypeUnbond** | 0x07 |
+----------------------------------+---------+
-| **TypeSetCandidateOnline** | 0x08 |
+| **TypeRedeemCheck** | 0x08 |
+----------------------------------+---------+
-| **TypeSetCandidateOffline** | 0x09 |
+| **TypeSetCandidateOnline** | 0x09 |
++----------------------------------+---------+
+| **TypeSetCandidateOffline** | 0x0A |
+----------------------------------+---------+
Send transaction
@@ -87,31 +89,52 @@ Transaction for sending arbitrary coin.
| **To** - Recipient address in Minter Network.
| **Value** - Amount of **Coin** to send.
-Convert transaction
-^^^^^^^^^^^^^^^^^^^
+Sell coin transaction
+^^^^^^^^^^^^^^^^^^^^^
Type: **0x02**
-Transaction for converting one coin (owned by sender) to another coin in a system.
+Transaction for selling one coin (owned by sender) in favour of another coin in a system.
+
+*Data field contents:*
+
+.. code-block:: go
+
+ type SellCoinData struct {
+ CoinToSell [10]byte
+ ValueToSell *big.Int
+ CoinToBuy [10]byte
+ }
+
+| **CoinToSell** - Symbol of a coin to give.
+| **ValueToSell** - Amount of **CoinToSell** to give.
+| **CoinToBuy** - Symbol of a coin to get.
+
+Buy coin transaction
+^^^^^^^^^^^^^^^^^^^^
+
+Type: **0x03**
+
+Transaction for buy a coin paying another coin (owned by sender).
*Data field contents:*
.. code-block:: go
- type ConvertData struct {
- FromCoinSymbol [10]byte
- ToCoinSymbol [10]byte
- Value *big.Int
+ type BuyCoinData struct {
+ CoinToBuy [10]byte
+ ValueToBuy *big.Int
+ CoinToSell [10]byte
}
-| **FromCoinSymbol** - Symbol of a coin to give.
-| **ToCoinSymbol** - Symbol of a coin to get.
-| **Value** - Amount of **FromCoinSymbol** to convert.
+| **CoinToBuy** - Symbol of a coin to get.
+| **ValueToBuy** - Amount of **CoinToBuy** to get.
+| **CoinToSell** - Symbol of a coin to give.
Create coin transaction
^^^^^^^^^^^^^^^^^^^^^^^
-Type: **0x03**
+Type: **0x04**
Transaction for creating new coin in a system.
@@ -136,7 +159,7 @@ Transaction for creating new coin in a system.
Declare candidacy transaction
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Type: **0x04**
+Type: **0x05**
Transaction for declaring new validator candidacy.
@@ -161,7 +184,7 @@ Transaction for declaring new validator candidacy.
Delegate transaction
^^^^^^^^^^^^^^^^^^^^
-Type: **0x05**
+Type: **0x06**
Transaction for delegating funds to validator.
@@ -179,12 +202,12 @@ Transaction for delegating funds to validator.
| **Coin** - Symbol of coin to stake.
| **Stake** - Amount of coins to stake.
-Unbound transaction
+Unbond transaction
^^^^^^^^^^^^^^^^^^^
-Type: **0x06**
+Type: **0x07**
-Transaction for unbounding funds from validator's stake.
+Transaction for unbonding funds from validator's stake.
*Data field contents:*
@@ -203,7 +226,7 @@ Transaction for unbounding funds from validator's stake.
Redeem check transaction
^^^^^^^^^^^^^^^^^^^^^^^^
-Type: **0x07**
+Type: **0x08**
Transaction for redeeming a check.
@@ -222,7 +245,7 @@ Transaction for redeeming a check.
Set candidate online transaction
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Type: **0x08**
+Type: **0x09**
Transaction for turning candidate on. This transaction should be sent from address which is set in the "Declare candidacy transaction".
@@ -239,7 +262,7 @@ Transaction for turning candidate on. This transaction should be sent from addre
Set candidate offline transaction
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-Type: **0x09**
+Type: **0x0A**
Transaction for turning candidate off. This transaction should be sent from address which is set in the "Declare candidacy transaction".
diff --git a/formula/formula.go b/formula/formula.go
index 44df704fb..1eff406a6 100644
--- a/formula/formula.go
+++ b/formula/formula.go
@@ -1,131 +1,17 @@
package formula
import (
- "github.com/ALTree/floatutils"
"github.com/MinterTeam/minter-go-node/core/types"
+ "github.com/MinterTeam/minter-go-node/math"
"math/big"
- "strings"
)
-var (
- One = big.NewInt(1)
- MaxWeight = uint(100)
- MinPrecision = 32
- MaxPrecision = 127
- Fixed1 = hexToBig("0x080000000000000000000000000000000")
- Fixed2 = hexToBig("0x100000000000000000000000000000000")
- Ln2Numerator = hexToBig("0x3f80fe03f80fe03f80fe03f80fe03f8")
- Ln2Denominator = hexToBig("0x5b9de1d10bf4103d647b0955897ba80")
- maxExpArray = make([]*big.Int, 128)
+const (
+ precision = 5000
)
-func initMaxExpArray() {
- maxExpArray[32] = hexToBig("0x1c35fedd14ffffffffffffffffffffffff")
- maxExpArray[33] = hexToBig("0x1b0ce43b323fffffffffffffffffffffff")
- maxExpArray[34] = hexToBig("0x19f0028ec1ffffffffffffffffffffffff")
- maxExpArray[35] = hexToBig("0x18ded91f0e7fffffffffffffffffffffff")
- maxExpArray[36] = hexToBig("0x17d8ec7f0417ffffffffffffffffffffff")
- maxExpArray[37] = hexToBig("0x16ddc6556cdbffffffffffffffffffffff")
- maxExpArray[38] = hexToBig("0x15ecf52776a1ffffffffffffffffffffff")
- maxExpArray[39] = hexToBig("0x15060c256cb2ffffffffffffffffffffff")
- maxExpArray[40] = hexToBig("0x1428a2f98d72ffffffffffffffffffffff")
- maxExpArray[41] = hexToBig("0x13545598e5c23fffffffffffffffffffff")
- maxExpArray[42] = hexToBig("0x1288c4161ce1dfffffffffffffffffffff")
- maxExpArray[43] = hexToBig("0x11c592761c666fffffffffffffffffffff")
- maxExpArray[44] = hexToBig("0x110a688680a757ffffffffffffffffffff")
- maxExpArray[45] = hexToBig("0x1056f1b5bedf77ffffffffffffffffffff")
- maxExpArray[46] = hexToBig("0x0faadceceeff8bffffffffffffffffffff")
- maxExpArray[47] = hexToBig("0x0f05dc6b27edadffffffffffffffffffff")
- maxExpArray[48] = hexToBig("0x0e67a5a25da4107fffffffffffffffffff")
- maxExpArray[49] = hexToBig("0x0dcff115b14eedffffffffffffffffffff")
- maxExpArray[50] = hexToBig("0x0d3e7a392431239fffffffffffffffffff")
- maxExpArray[51] = hexToBig("0x0cb2ff529eb71e4fffffffffffffffffff")
- maxExpArray[52] = hexToBig("0x0c2d415c3db974afffffffffffffffffff")
- maxExpArray[53] = hexToBig("0x0bad03e7d883f69bffffffffffffffffff")
- maxExpArray[54] = hexToBig("0x0b320d03b2c343d5ffffffffffffffffff")
- maxExpArray[55] = hexToBig("0x0abc25204e02828dffffffffffffffffff")
- maxExpArray[56] = hexToBig("0x0a4b16f74ee4bb207fffffffffffffffff")
- maxExpArray[57] = hexToBig("0x09deaf736ac1f569ffffffffffffffffff")
- maxExpArray[58] = hexToBig("0x0976bd9952c7aa957fffffffffffffffff")
- maxExpArray[59] = hexToBig("0x09131271922eaa606fffffffffffffffff")
- maxExpArray[60] = hexToBig("0x08b380f3558668c46fffffffffffffffff")
- maxExpArray[61] = hexToBig("0x0857ddf0117efa215bffffffffffffffff")
- maxExpArray[62] = hexToBig("0x07ffffffffffffffffffffffffffffffff")
- maxExpArray[63] = hexToBig("0x07abbf6f6abb9d087fffffffffffffffff")
- maxExpArray[64] = hexToBig("0x075af62cbac95f7dfa7fffffffffffffff")
- maxExpArray[65] = hexToBig("0x070d7fb7452e187ac13fffffffffffffff")
- maxExpArray[66] = hexToBig("0x06c3390ecc8af379295fffffffffffffff")
- maxExpArray[67] = hexToBig("0x067c00a3b07ffc01fd6fffffffffffffff")
- maxExpArray[68] = hexToBig("0x0637b647c39cbb9d3d27ffffffffffffff")
- maxExpArray[69] = hexToBig("0x05f63b1fc104dbd39587ffffffffffffff")
- maxExpArray[70] = hexToBig("0x05b771955b36e12f7235ffffffffffffff")
- maxExpArray[71] = hexToBig("0x057b3d49dda84556d6f6ffffffffffffff")
- maxExpArray[72] = hexToBig("0x054183095b2c8ececf30ffffffffffffff")
- maxExpArray[73] = hexToBig("0x050a28be635ca2b888f77fffffffffffff")
- maxExpArray[74] = hexToBig("0x04d5156639708c9db33c3fffffffffffff")
- maxExpArray[75] = hexToBig("0x04a23105873875bd52dfdfffffffffffff")
- maxExpArray[76] = hexToBig("0x0471649d87199aa990756fffffffffffff")
- maxExpArray[77] = hexToBig("0x04429a21a029d4c1457cfbffffffffffff")
- maxExpArray[78] = hexToBig("0x0415bc6d6fb7dd71af2cb3ffffffffffff")
- maxExpArray[79] = hexToBig("0x03eab73b3bbfe282243ce1ffffffffffff")
- maxExpArray[80] = hexToBig("0x03c1771ac9fb6b4c18e229ffffffffffff")
- maxExpArray[81] = hexToBig("0x0399e96897690418f785257fffffffffff")
- maxExpArray[82] = hexToBig("0x0373fc456c53bb779bf0ea9fffffffffff")
- maxExpArray[83] = hexToBig("0x034f9e8e490c48e67e6ab8bfffffffffff")
- maxExpArray[84] = hexToBig("0x032cbfd4a7adc790560b3337ffffffffff")
- maxExpArray[85] = hexToBig("0x030b50570f6e5d2acca94613ffffffffff")
- maxExpArray[86] = hexToBig("0x02eb40f9f620fda6b56c2861ffffffffff")
- maxExpArray[87] = hexToBig("0x02cc8340ecb0d0f520a6af58ffffffffff")
- maxExpArray[88] = hexToBig("0x02af09481380a0a35cf1ba02ffffffffff")
- maxExpArray[89] = hexToBig("0x0292c5bdd3b92ec810287b1b3fffffffff")
- maxExpArray[90] = hexToBig("0x0277abdcdab07d5a77ac6d6b9fffffffff")
- maxExpArray[91] = hexToBig("0x025daf6654b1eaa55fd64df5efffffffff")
- maxExpArray[92] = hexToBig("0x0244c49c648baa98192dce88b7ffffffff")
- maxExpArray[93] = hexToBig("0x022ce03cd5619a311b2471268bffffffff")
- maxExpArray[94] = hexToBig("0x0215f77c045fbe885654a44a0fffffffff")
- maxExpArray[95] = hexToBig("0x01ffffffffffffffffffffffffffffffff")
- maxExpArray[96] = hexToBig("0x01eaefdbdaaee7421fc4d3ede5ffffffff")
- maxExpArray[97] = hexToBig("0x01d6bd8b2eb257df7e8ca57b09bfffffff")
- maxExpArray[98] = hexToBig("0x01c35fedd14b861eb0443f7f133fffffff")
- maxExpArray[99] = hexToBig("0x01b0ce43b322bcde4a56e8ada5afffffff")
- maxExpArray[100] = hexToBig("0x019f0028ec1fff007f5a195a39dfffffff")
- maxExpArray[101] = hexToBig("0x018ded91f0e72ee74f49b15ba527ffffff")
- maxExpArray[102] = hexToBig("0x017d8ec7f04136f4e5615fd41a63ffffff")
- maxExpArray[103] = hexToBig("0x016ddc6556cdb84bdc8d12d22e6fffffff")
- maxExpArray[104] = hexToBig("0x015ecf52776a1155b5bd8395814f7fffff")
- maxExpArray[105] = hexToBig("0x015060c256cb23b3b3cc3754cf40ffffff")
- maxExpArray[106] = hexToBig("0x01428a2f98d728ae223ddab715be3fffff")
- maxExpArray[107] = hexToBig("0x013545598e5c23276ccf0ede68034fffff")
- maxExpArray[108] = hexToBig("0x01288c4161ce1d6f54b7f61081194fffff")
- maxExpArray[109] = hexToBig("0x011c592761c666aa641d5a01a40f17ffff")
- maxExpArray[110] = hexToBig("0x0110a688680a7530515f3e6e6cfdcdffff")
- maxExpArray[111] = hexToBig("0x01056f1b5bedf75c6bcb2ce8aed428ffff")
- maxExpArray[112] = hexToBig("0x00faadceceeff8a0890f3875f008277fff")
- maxExpArray[113] = hexToBig("0x00f05dc6b27edad306388a600f6ba0bfff")
- maxExpArray[114] = hexToBig("0x00e67a5a25da41063de1495d5b18cdbfff")
- maxExpArray[115] = hexToBig("0x00dcff115b14eedde6fc3aa5353f2e4fff")
- maxExpArray[116] = hexToBig("0x00d3e7a3924312399f9aae2e0f868f8fff")
- maxExpArray[117] = hexToBig("0x00cb2ff529eb71e41582cccd5a1ee26fff")
- maxExpArray[118] = hexToBig("0x00c2d415c3db974ab32a51840c0b67edff")
- maxExpArray[119] = hexToBig("0x00bad03e7d883f69ad5b0a186184e06bff")
- maxExpArray[120] = hexToBig("0x00b320d03b2c343d4829abd6075f0cc5ff")
- maxExpArray[121] = hexToBig("0x00abc25204e02828d73c6e80bcdb1a95bf")
- maxExpArray[122] = hexToBig("0x00a4b16f74ee4bb2040a1ec6c15fbbf2df")
- maxExpArray[123] = hexToBig("0x009deaf736ac1f569deb1b5ae3f36c130f")
- maxExpArray[124] = hexToBig("0x00976bd9952c7aa957f5937d790ef65037")
- maxExpArray[125] = hexToBig("0x009131271922eaa6064b73a22d0bd4f2bf")
- maxExpArray[126] = hexToBig("0x008b380f3558668c46c91c49a2f8e967b9")
- maxExpArray[127] = hexToBig("0x00857ddf0117efa215952912839f6473e6")
-}
-
-func init() {
- initMaxExpArray()
-}
-
-func hexToBig(hex string) *big.Int {
- ret, _ := big.NewInt(0).SetString(strings.TrimLeft(strings.TrimLeft(hex, "0x"), "0"), 16)
-
- return ret
+func newFloat(x float64) *big.Float {
+ return big.NewFloat(x).SetPrec(precision)
}
// Return = supply * ((1 + deposit / reserve) ^ (crr / 100) - 1)
@@ -140,21 +26,41 @@ func CalculatePurchaseReturn(supply *big.Int, reserve *big.Int, crr uint, deposi
return result.Div(result, reserve)
}
- result := big.NewInt(0)
- var precision uint
+ tSupply := newFloat(0).SetInt(supply)
+ tReserve := newFloat(0).SetInt(reserve)
+ tDeposit := newFloat(0).SetInt(deposit)
- baseN := big.NewInt(0).Add(deposit, reserve)
+ res := newFloat(0).Quo(tDeposit, tReserve) // deposit / reserve
+ res.Add(res, newFloat(1)) // 1 + (deposit / reserve)
+ res = math.Pow(res, newFloat(float64(crr)/100)) // (1 + deposit / reserve) ^ (crr / 100)
+ res.Sub(res, newFloat(1)) // ((1 + deposit / reserve) ^ (crr / 100) - 1)
+ res.Mul(res, tSupply) // supply * ((1 + deposit / reserve) ^ (crr / 100) - 1)
- result, precision = power(baseN, reserve, crr, MaxWeight)
+ result, _ := res.Int(nil)
- temp := big.NewInt(0).Mul(supply, result)
- temp.Rsh(temp, precision)
+ return result
+}
+
+// reversed function CalculatePurchaseReturn
+// deposit = reserve * (((wantReceive + supply) / supply)^(100/c) - 1)
+func CalculatePurchaseAmount(supply *big.Int, reserve *big.Int, crr uint, wantReceive *big.Int) *big.Int {
+
+ tSupply := newFloat(0).SetInt(supply)
+ tReserve := newFloat(0).SetInt(reserve)
+ tWantReceive := newFloat(0).SetInt(wantReceive)
- return temp.Sub(temp, supply)
+ res := newFloat(0).Add(tWantReceive, tSupply) // reserve + supply
+ res.Quo(res, tSupply) // (reserve + supply) / supply
+ res = math.Pow(res, newFloat(100/float64(crr))) // ((reserve + supply) / supply)^(100/c)
+ res.Sub(res, newFloat(1)) // (((reserve + supply) / supply)^(100/c) - 1)
+ res.Mul(res, tReserve) // reserve * (((reserve + supply) / supply)^(100/c) - 1)
+ result, _ := res.Int(nil)
+
+ return result
}
-// Return = reserve * (1 - (1 - sellAmount / supply) ^ (1 / (crr / 100)))
+// Return = reserve * (1 - (1 - sellAmount / supply) ^ (100 / crr))
func CalculateSaleReturn(supply *big.Int, reserve *big.Int, crr uint, sellAmount *big.Int) *big.Int {
// special case for 0 sell amount
@@ -174,231 +80,37 @@ func CalculateSaleReturn(supply *big.Int, reserve *big.Int, crr uint, sellAmount
return ret
}
- result := big.NewInt(0)
- var precision uint
+ tSupply := newFloat(0).SetInt(supply)
+ tReserve := newFloat(0).SetInt(reserve)
+ tSellAmount := newFloat(0).SetInt(sellAmount)
- baseD := big.NewInt(0).Sub(supply, sellAmount)
+ res := newFloat(0).Quo(tSellAmount, tSupply) // sellAmount / supply
+ res.Sub(newFloat(1), res) // (1 - sellAmount / supply)
+ res = math.Pow(res, newFloat(100/(float64(crr)))) // (1 - sellAmount / supply) ^ (100 / crr)
+ res.Sub(newFloat(1), res) // (1 - (1 - sellAmount / supply) ^ (1 / (crr / 100)))
+ res.Mul(res, tReserve) // reserve * (1 - (1 - sellAmount / supply) ^ (1 / (crr / 100)))
- result, precision = power(supply, baseD, MaxWeight, crr)
-
- temp1 := big.NewInt(0).Mul(reserve, result)
- temp2 := big.NewInt(0).Lsh(reserve, precision)
-
- res := big.NewInt(0).Sub(temp1, temp2)
- res.Div(res, result)
+ result, _ := res.Int(nil)
- return res
+ return result
}
+// reversed function CalculateSaleReturn
func CalculateSaleAmount(supply *big.Int, reserve *big.Int, crr uint, wantReceive *big.Int) *big.Int {
- tSupply := big.NewFloat(0).SetInt(supply)
- tReserve := big.NewFloat(0).SetInt(reserve)
- tWantReceive := big.NewFloat(0).SetInt(wantReceive)
+ tSupply := newFloat(0).SetInt(supply)
+ tReserve := newFloat(0).SetInt(reserve)
+ tWantReceive := newFloat(0).SetInt(wantReceive)
- res := big.NewFloat(0).Sub(tWantReceive, tReserve)
- res.Mul(res, big.NewFloat(-1))
+ res := newFloat(0).Sub(tWantReceive, tReserve)
+ res.Mul(res, newFloat(-1))
res.Quo(res, tReserve)
- res = bigfloat.Pow(res, big.NewFloat(float64(crr)/100))
- res.Add(res, big.NewFloat(-1))
- res.Mul(res, big.NewFloat(-1))
+ res = math.Pow(res, newFloat(float64(crr)/100))
+ res.Add(res, newFloat(-1))
+ res.Mul(res, newFloat(-1))
res.Mul(res, tSupply)
result, _ := res.Int(nil)
return result
}
-
-func power(aBaseN *big.Int, aBaseD *big.Int, aExpN uint, aExpD uint) (*big.Int, uint) {
- lnBaseTimesExp := ln(aBaseN, aBaseD)
- lnBaseTimesExp.Mul(lnBaseTimesExp, big.NewInt(int64(aExpN)))
- lnBaseTimesExp.Div(lnBaseTimesExp, big.NewInt(int64(aExpD)))
-
- precision := findPositionInMaxExpArray(lnBaseTimesExp)
-
- return fixedExp(lnBaseTimesExp.Rsh(lnBaseTimesExp, uint(MaxPrecision)-precision), precision), precision
-}
-
-func ln(aNumerator *big.Int, aDenominator *big.Int) *big.Int {
-
- res := big.NewInt(0)
- x := big.NewInt(0).Mul(aNumerator, Fixed1)
- x.Div(x, aDenominator)
-
- if x.Cmp(Fixed2) != -1 {
- count := floorLog2(big.NewInt(0).Div(x, Fixed1))
- x.Rsh(x, count)
- res.Mul(big.NewInt(int64(count)), Fixed1)
- }
-
- if x.Cmp(Fixed1) == 1 {
- for i := MaxPrecision; i > 0; i -= 1 {
- x.Mul(x, x)
- x.Div(x, Fixed1)
-
- if x.Cmp(Fixed2) != -1 {
- x.Rsh(x, 1)
- res.Add(res, big.NewInt(1).Lsh(big.NewInt(1), uint(i-1)))
- }
- }
- }
-
- res.Mul(res, Ln2Numerator)
- res.Div(res, Ln2Denominator)
-
- return res
-}
-
-func floorLog2(aN *big.Int) uint {
- n := big.NewInt(0).Set(aN)
- res := 0
-
- if n.Cmp(big.NewInt(256)) == -1 {
- for n.Cmp(big.NewInt(1)) == 1 {
- n.Rsh(n, 1)
- res += 1
- }
- } else {
- for s := 128; s > 0; s >>= 1 {
- if n.Cmp(big.NewInt(1).Lsh(One, uint(s))) != -1 {
- n.Rsh(n, uint(s))
- res |= s
- }
- }
- }
-
- return uint(res)
-}
-
-func findPositionInMaxExpArray(x *big.Int) uint {
- lo := MinPrecision
- hi := MaxPrecision
-
- for lo+1 < hi {
- mid := (lo + hi) / 2
-
- if maxExpArray[mid].Cmp(x) != -1 {
- lo = mid
- } else {
- hi = mid
- }
- }
-
- if maxExpArray[hi].Cmp(x) != -1 {
- return uint(hi)
- }
-
- if maxExpArray[lo].Cmp(x) != -1 {
- return uint(lo)
- }
-
- return 0
-}
-
-func fixedExp(x *big.Int, precision uint) *big.Int {
- xi := big.NewInt(0).Set(x)
- res := big.NewInt(0)
-
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x03442c4e6074a82f1797f72ac0000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0116b96f757c380fb287fd0e40000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0045ae5bdd5f0e03eca1ff4390000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000defabf91302cd95b9ffda50000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0002529ca9832b22439efff9b8000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000054f1cf12bd04e516b6da88000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000a9e39e257a09ca2d6db51000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000012e066e7b839fa050c309000000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000001e33d7d926c329a1ad1a800000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000002bee513bdb4a6b19b5f800000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000003a9316fa79b88eccf2a00000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000048177ebe1fa812375200000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000005263fe90242dcbacf00000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000000000057e22099c030d94100000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000057e22099c030d9410000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000052b6b54569976310000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000004985f67696bf748000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000000000000003dea12ea99e498000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000000031880f2214b6e000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000000000000000025bcff56eb36000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000000000000000001b722e10ab1000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000001317c70077000")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000000000000cba84aafa00")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000000000000082573a0a00")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000000000000005035ad900")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x0000000000000000000000002f881b00")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000000000001b29340")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x000000000000000000000000000efc40")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000000000000007fe0")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000000000000000420")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000000000000000021")))
- xi.Mul(xi, x)
- xi.Rsh(xi, precision)
- res.Add(res, big.NewInt(0).Mul(xi, hexToBig("0x00000000000000000000000000000001")))
-
- ret := big.NewInt(0).Div(res, hexToBig("0x688589cc0e9505e2f2fee5580000000"))
- ret.Add(ret, x)
- ret.Add(ret, big.NewInt(0).Lsh(One, precision))
-
- return ret
-}
diff --git a/log/log.go b/log/log.go
new file mode 100644
index 000000000..5dc2973f7
--- /dev/null
+++ b/log/log.go
@@ -0,0 +1,27 @@
+package log
+
+import (
+ "github.com/tendermint/tendermint/libs/log"
+ "os"
+)
+
+var (
+ logger log.Logger
+)
+
+func init() {
+ logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
+ SetLogger(logger)
+}
+
+func SetLogger(l log.Logger) {
+ logger = l
+}
+
+func Info(msg string, ctx ...interface{}) {
+ logger.Info(msg, ctx...)
+}
+
+func With(keyvals ...interface{}) log.Logger {
+ return logger.With(keyvals...)
+}
diff --git a/math/exp.go b/math/exp.go
new file mode 100644
index 000000000..e02e1dc33
--- /dev/null
+++ b/math/exp.go
@@ -0,0 +1,56 @@
+package math
+
+import (
+ "math"
+ "math/big"
+)
+
+// https://github.com/ALTree/bigfloat
+
+// Exp returns a big.Float representation of exp(z). Precision is
+// the same as the one of the argument. The function returns +Inf
+// when z = +Inf, and 0 when z = -Inf.
+func ExpFloat(z *big.Float) *big.Float {
+
+ // exp(0) == 1
+ if z.Sign() == 0 {
+ return big.NewFloat(1).SetPrec(z.Prec())
+ }
+
+ // Exp(+Inf) = +Inf
+ if z.IsInf() && z.Sign() > 0 {
+ return big.NewFloat(math.Inf(+1)).SetPrec(z.Prec())
+ }
+
+ // Exp(-Inf) = 0
+ if z.IsInf() && z.Sign() < 0 {
+ return big.NewFloat(0).SetPrec(z.Prec())
+ }
+
+ guess := new(big.Float)
+
+ // try to get initial estimate using IEEE-754 math
+ zf, _ := z.Float64()
+ if zfs := math.Exp(zf); zfs == math.Inf(+1) || zfs == 0 {
+ // too big or too small for IEEE-754 math,
+ // perform argument reduction using
+ // e^{2z} = (e^z)²
+ halfZ := new(big.Float).Mul(z, big.NewFloat(0.5))
+ halfExp := ExpFloat(halfZ.SetPrec(z.Prec() + 64))
+ return new(big.Float).Mul(halfExp, halfExp).SetPrec(z.Prec())
+ } else {
+ // we got a nice IEEE-754 estimate
+ guess.SetFloat64(zfs)
+ }
+
+ // f(t)/f'(t) = t*(log(t) - z)
+ f := func(t *big.Float) *big.Float {
+ x := new(big.Float)
+ x.Sub(Log(t), z)
+ return x.Mul(x, t)
+ }
+
+ x := newton(f, guess, z.Prec())
+
+ return x
+}
diff --git a/math/integer.go b/math/integer.go
deleted file mode 100644
index 7eff4d3b0..000000000
--- a/math/integer.go
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package math
-
-import (
- "fmt"
- "strconv"
-)
-
-const (
- // Integer limit values.
- MaxInt8 = 1<<7 - 1
- MinInt8 = -1 << 7
- MaxInt16 = 1<<15 - 1
- MinInt16 = -1 << 15
- MaxInt32 = 1<<31 - 1
- MinInt32 = -1 << 31
- MaxInt64 = 1<<63 - 1
- MinInt64 = -1 << 63
- MaxUint8 = 1<<8 - 1
- MaxUint16 = 1<<16 - 1
- MaxUint32 = 1<<32 - 1
- MaxUint64 = 1<<64 - 1
-)
-
-// HexOrDecimal64 marshals uint64 as hex or decimal.
-type HexOrDecimal64 uint64
-
-// UnmarshalText implements encoding.TextUnmarshaler.
-func (i *HexOrDecimal64) UnmarshalText(input []byte) error {
- int, ok := ParseUint64(string(input))
- if !ok {
- return fmt.Errorf("invalid hex or decimal integer %q", input)
- }
- *i = HexOrDecimal64(int)
- return nil
-}
-
-// MarshalText implements encoding.TextMarshaler.
-func (i HexOrDecimal64) MarshalText() ([]byte, error) {
- return []byte(fmt.Sprintf("%#x", uint64(i))), nil
-}
-
-// ParseUint64 parses s as an integer in decimal or hexadecimal syntax.
-// Leading zeros are accepted. The empty string parses as zero.
-func ParseUint64(s string) (uint64, bool) {
- if s == "" {
- return 0, true
- }
- if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") {
- v, err := strconv.ParseUint(s[2:], 16, 64)
- return v, err == nil
- }
- v, err := strconv.ParseUint(s, 10, 64)
- return v, err == nil
-}
-
-// MustParseUint64 parses s as an integer and panics if the string is invalid.
-func MustParseUint64(s string) uint64 {
- v, ok := ParseUint64(s)
- if !ok {
- panic("invalid unsigned 64 bit integer: " + s)
- }
- return v
-}
-
-// NOTE: The following methods need to be optimised using either bit checking or asm
-
-// SafeSub returns subtraction result and whether overflow occurred.
-func SafeSub(x, y uint64) (uint64, bool) {
- return x - y, x < y
-}
-
-// SafeAdd returns the result and whether overflow occurred.
-func SafeAdd(x, y uint64) (uint64, bool) {
- return x + y, y > MaxUint64-x
-}
-
-// SafeMul returns multiplication result and whether overflow occurred.
-func SafeMul(x, y uint64) (uint64, bool) {
- if x == 0 || y == 0 {
- return 0, false
- }
- return x * y, y > MaxUint64/x
-}
diff --git a/math/log.go b/math/log.go
new file mode 100644
index 000000000..b803d1a8e
--- /dev/null
+++ b/math/log.go
@@ -0,0 +1,87 @@
+package math
+
+import (
+ "math"
+ "math/big"
+)
+
+// https://github.com/ALTree/bigfloat
+
+// Log returns a big.Float representation of the natural logarithm of
+// z. Precision is the same as the one of the argument. The function
+// panics if z is negative, returns -Inf when z = 0, and +Inf when z =
+// +Inf
+func Log(z *big.Float) *big.Float {
+
+ // panic on negative z
+ if z.Sign() == -1 {
+ panic("Log: argument is negative")
+ }
+
+ // Log(0) = -Inf
+ if z.Sign() == 0 {
+ return big.NewFloat(math.Inf(-1)).SetPrec(z.Prec())
+ }
+
+ prec := z.Prec() + 64 // guard digits
+
+ one := big.NewFloat(1).SetPrec(prec)
+ two := big.NewFloat(2).SetPrec(prec)
+ four := big.NewFloat(4).SetPrec(prec)
+
+ // Log(1) = 0
+ if z.Cmp(one) == 0 {
+ return big.NewFloat(0).SetPrec(z.Prec())
+ }
+
+ // Log(+Inf) = +Inf
+ if z.IsInf() {
+ return big.NewFloat(math.Inf(+1)).SetPrec(z.Prec())
+ }
+
+ x := new(big.Float).SetPrec(prec)
+
+ // if 0 < z < 1 we compute log(z) as -log(1/z)
+ var neg bool
+ if z.Cmp(one) < 0 {
+ x.Quo(one, z)
+ neg = true
+ } else {
+ x.Set(z)
+ }
+
+ // We scale up x until x >= 2**(prec/2), and then we'll be allowed
+ // to use the AGM formula for Log(x).
+ //
+ // Double x until the condition is met, and keep track of the
+ // number of doubling we did (needed to scale back later).
+
+ lim := new(big.Float)
+ lim.SetMantExp(two, int(prec/2))
+
+ k := 0
+ for x.Cmp(lim) < 0 {
+ x.Mul(x, x)
+ k++
+ }
+
+ // Compute the natural log of x using the fact that
+ // log(x) = π / (2 * AGM(1, 4/x))
+ // if
+ // x >= 2**(prec/2),
+ // where prec is the desired precision (in bits)
+ pi := pi(prec)
+ agm := agm(one, x.Quo(four, x)) // agm = AGM(1, 4/x)
+
+ x.Quo(pi, x.Mul(two, agm)) // reuse x, we don't need it
+
+ if neg {
+ x.Neg(x)
+ }
+
+ // scale the result back multiplying by 2**-k
+ // reuse lim to reduce allocations.
+ x.Mul(x, lim.SetMantExp(one, -k))
+
+ return x.SetPrec(z.Prec())
+}
diff --git a/math/math.go b/math/math.go
new file mode 100644
index 000000000..f7f2dc6ca
--- /dev/null
+++ b/math/math.go
@@ -0,0 +1,109 @@
+// Package bigfloat provides the implementation of a few additional operations for the
+// standard library big.Float type.
+package math
+
+import (
+ "math"
+ "math/big"
+)
+
+// Sqrt returns a big.Float representation of the square root of
+// z. Precision is the same as the one of the argument. The function
+// panics if z is negative, returns ±0 when z = ±0, and +Inf when z =
+// +Inf.
+func Sqrt(z *big.Float) *big.Float {
+
+ // panic on negative z
+ if z.Sign() == -1 {
+ panic("Sqrt: argument is negative")
+ }
+
+ // √±0 = ±0
+ if z.Sign() == 0 {
+ return big.NewFloat(float64(z.Sign()))
+ }
+
+ // √+Inf = +Inf
+ if z.IsInf() {
+ return big.NewFloat(math.Inf(+1))
+ }
+
+ // Compute √(a·2**b) as
+ // √(a)·2**b/2 if b is even
+ // √(2a)·2**b/2 if b > 0 is odd
+ // √(0.5a)·2**b/2 if b < 0 is odd
+ //
+ // The difference in the odd exponent case is due to the fact that
+ // exp/2 is rounded in different directions when exp is negative.
+ mant := new(big.Float)
+ exp := z.MantExp(mant)
+ switch exp % 2 {
+ case 1:
+ mant.Mul(big.NewFloat(2), mant)
+ case -1:
+ mant.Mul(big.NewFloat(0.5), mant)
+ }
+
+ // Solving x² - z = 0 directly requires a Quo call, but it's
+ // faster for small precisions.
+ //
+ // Solving 1/x² - z = 0 avoids the Quo call and is much faster for
+ // high precisions.
+ //
+ // Use sqrtDirect for prec <= 128 and sqrtInverse for prec > 128.
+ var x *big.Float
+ if z.Prec() <= 128 {
+ x = sqrtDirect(mant)
+ } else {
+ x = sqrtInverse(mant)
+ }
+
+ // re-attach the exponent and return
+ return x.SetMantExp(x, exp/2)
+
+}
+
+// compute √z using newton to solve
+// t² - z = 0 for t
+func sqrtDirect(z *big.Float) *big.Float {
+ // f(t)/f'(t) = 0.5(t² - z)/t
+ half := big.NewFloat(0.5)
+ f := func(t *big.Float) *big.Float {
+ x := new(big.Float).Mul(t, t) // x = t²
+ x.Sub(x, z) // x = t² - z
+ x.Mul(half, x) // x = 0.5(t² - z)
+ return x.Quo(x, t) // return x = 0.5(t² - z)/t
+ }
+
+ // initial guess
+ zf, _ := z.Float64()
+ guess := big.NewFloat(math.Sqrt(zf))
+
+ return newton(f, guess, z.Prec())
+}
+
+// compute √z using newton to solve
+// 1/t² - z = 0 for x and then inverting.
+func sqrtInverse(z *big.Float) *big.Float {
+ // f(t)/f'(t) = -0.5t(1 - zt²)
+ nhalf := big.NewFloat(-0.5)
+ one := big.NewFloat(1)
+ f := func(t *big.Float) *big.Float {
+ u := new(big.Float)
+ u.Mul(t, t) // u = t²
+ u.Mul(u, z) // u = zt²
+ u.Sub(one, u) // u = 1 - zt²
+ u.Mul(u, nhalf) // u = -0.5(1 - zt²)
+ return new(big.Float).Mul(t, u) // x = -0.5t(1 - zt²)
+ }
+
+ // initial guess
+ zf, _ := z.Float64()
+ guess := big.NewFloat(1 / math.Sqrt(zf))
+
+ // There's another operation after newton,
+ // so we need to force it to return at least
+ // a few guard digits. Use 32.
+ x := newton(f, guess, z.Prec()+32)
+ return x.Mul(z, x).SetPrec(z.Prec())
+}
diff --git a/math/misc.go b/math/misc.go
new file mode 100644
index 000000000..0ffc96468
--- /dev/null
+++ b/math/misc.go
@@ -0,0 +1,130 @@
+package math
+
+import "math/big"
+
+// https://github.com/ALTree/bigfloat
+
+// agm returns the arithmetic-geometric mean of a and b.
+// a and b must have the same precision.
+func agm(a, b *big.Float) *big.Float {
+
+ if a.Prec() != b.Prec() {
+ panic("agm: different precisions")
+ }
+
+ prec := a.Prec()
+
+ // do not overwrite a and b
+ a2 := new(big.Float).Copy(a).SetPrec(prec + 64)
+ b2 := new(big.Float).Copy(b).SetPrec(prec + 64)
+
+ if a2.Cmp(b2) == -1 {
+ a2, b2 = b2, a2
+ }
+ // a2 >= b2
+
+ // set lim to 2**(-prec)
+ lim := new(big.Float)
+ lim.SetMantExp(big.NewFloat(1).SetPrec(prec+64), -int(prec+1))
+
+ half := big.NewFloat(0.5)
+ t := new(big.Float)
+
+ for t.Sub(a2, b2).Cmp(lim) != -1 {
+ t.Copy(a2)
+ a2.Add(a2, b2).Mul(a2, half)
+ b2 = Sqrt(b2.Mul(b2, t))
+ }
+
+ return a2.SetPrec(prec)
+}
+
+var piCache *big.Float
+var piCachePrec uint
+var enablePiCache bool = true
+
+func init() {
+ if !enablePiCache {
+ return
+ }
+
+ piCache, _, _ = new(big.Float).SetPrec(1024).Parse("3."+
+ "14159265358979323846264338327950288419716939937510"+
+ "58209749445923078164062862089986280348253421170679"+
+ "82148086513282306647093844609550582231725359408128"+
+ "48111745028410270193852110555964462294895493038196"+
+ "44288109756659334461284756482337867831652712019091"+
+ "45648566923460348610454326648213393607260249141273"+
+ "72458700660631558817488152092096282925409171536444", 10)
+
+ piCachePrec = 1024
+}
+
+// pi returns pi to prec bits of precision
+func pi(prec uint) *big.Float {
+
+ if prec <= piCachePrec && enablePiCache {
+ return new(big.Float).Copy(piCache).SetPrec(prec)
+ }
+
+ // Following R. P. Brent, Multiple-precision zero-finding
+ // methods and the complexity of elementary function evaluation,
+ // in Analytic Computational Complexity, Academic Press,
+ // New York, 1975, Section 8.
+
+ half := big.NewFloat(0.5)
+ two := big.NewFloat(2).SetPrec(prec + 64)
+
+ // initialization
+ a := big.NewFloat(1).SetPrec(prec + 64) // a = 1
+ b := new(big.Float).Mul(Sqrt(two), half) // b = 1/√2
+ t := big.NewFloat(0.25).SetPrec(prec + 64) // t = 1/4
+ x := big.NewFloat(1).SetPrec(prec + 64) // x = 1
+
+ // limit is 2**(-prec)
+ lim := new(big.Float)
+ lim.SetMantExp(big.NewFloat(1).SetPrec(prec+64), -int(prec+1))
+
+ // temp variables
+ y := new(big.Float)
+ for y.Sub(a, b).Cmp(lim) != -1 { // assume a > b
+ y.Copy(a)
+ a.Add(a, b).Mul(a, half) // a = (a+b)/2
+ b = Sqrt(b.Mul(b, y)) // b = √(ab)
+
+ y.Sub(a, y) // y = a - y
+ y.Mul(y, y).Mul(y, x) // y = x(a-y)²
+ t.Sub(t, y) // t = t - x(a-y)²
+ x.Mul(x, two) // x = 2x
+ }
+
+ a.Mul(a, a).Quo(a, t) // π = a² / t
+ a.SetPrec(prec)
+
+ if enablePiCache {
+ piCache.Copy(a)
+ piCachePrec = prec
+ }
+
+ return a
+}
+
+// returns an approximate (to precision dPrec) solution to
+// f(t) = 0
+// using the Newton Method.
+// fOverDf needs to be a fuction returning f(t)/f'(t).
+// t must not be changed by fOverDf.
+// guess is the initial guess (and it's not preserved).
+func newton(fOverDf func(z *big.Float) *big.Float, guess *big.Float, dPrec uint) *big.Float {
+
+ prec, guard := guess.Prec(), uint(64)
+ guess.SetPrec(prec + guard)
+
+ for prec < 2*dPrec {
+ guess.Sub(guess, fOverDf(guess))
+ prec *= 2
+ guess.SetPrec(prec + guard)
+ }
+
+ return guess.SetPrec(dPrec)
+}
diff --git a/math/pow.go b/math/pow.go
new file mode 100644
index 000000000..1ea0c6a21
--- /dev/null
+++ b/math/pow.go
@@ -0,0 +1,41 @@
+package math
+
+import "math/big"
+
+// https://github.com/ALTree/bigfloat
+
+// Pow returns a big.Float representation of z**w. Precision is the same as the one
+// of the first argument. The function panics when z is negative.
+func Pow(z *big.Float, w *big.Float) *big.Float {
+
+ if z.Sign() < 0 {
+ panic("Pow: negative base")
+ }
+
+ // Pow(z, 0) = 1.0
+ if w.Sign() == 0 {
+ return big.NewFloat(1).SetPrec(z.Prec())
+ }
+
+ // Pow(z, 1) = z
+ // Pow(+Inf, n) = +Inf
+ if w.Cmp(big.NewFloat(1)) == 0 || z.IsInf() {
+ return new(big.Float).Copy(z)
+ }
+
+ // Pow(z, -w) = 1 / Pow(z, w)
+ if w.Sign() < 0 {
+ x := new(big.Float)
+ zExt := new(big.Float).Copy(z).SetPrec(z.Prec() + 64)
+ wNeg := new(big.Float).Neg(w)
+ return x.Quo(big.NewFloat(1), Pow(zExt, wNeg)).SetPrec(z.Prec())
+ }
+
+ // compute w**z as exp(z log(w))
+ x := new(big.Float).SetPrec(z.Prec() + 64)
+ logZ := Log(new(big.Float).Copy(z).SetPrec(z.Prec() + 64))
+ x.Mul(w, logZ)
+ x = ExpFloat(x)
+ return x.SetPrec(z.Prec())
+
+}
diff --git a/metrics/disk.go b/metrics/disk.go
deleted file mode 100644
index 25142d2ad..000000000
--- a/metrics/disk.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package metrics
-
-// DiskStats is the per process disk io stats.
-type DiskStats struct {
- ReadCount int64 // Number of read operations executed
- ReadBytes int64 // Total number of bytes read
- WriteCount int64 // Number of write operations executed
- WriteBytes int64 // Total number of byte written
-}
diff --git a/metrics/disk_linux.go b/metrics/disk_linux.go
deleted file mode 100644
index 8d610cd67..000000000
--- a/metrics/disk_linux.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Contains the Linux implementation of process disk IO counter retrieval.
-
-package metrics
-
-import (
- "bufio"
- "fmt"
- "io"
- "os"
- "strconv"
- "strings"
-)
-
-// ReadDiskStats retrieves the disk IO stats belonging to the current process.
-func ReadDiskStats(stats *DiskStats) error {
- // Open the process disk IO counter file
- inf, err := os.Open(fmt.Sprintf("/proc/%d/io", os.Getpid()))
- if err != nil {
- return err
- }
- defer inf.Close()
- in := bufio.NewReader(inf)
-
- // Iterate over the IO counter, and extract what we need
- for {
- // Read the next line and split to key and value
- line, err := in.ReadString('\n')
- if err != nil {
- if err == io.EOF {
- return nil
- }
- return err
- }
- parts := strings.Split(line, ":")
- if len(parts) != 2 {
- continue
- }
- key := strings.TrimSpace(parts[0])
- value, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
- if err != nil {
- return err
- }
-
- // Update the counter based on the key
- switch key {
- case "syscr":
- stats.ReadCount = value
- case "syscw":
- stats.WriteCount = value
- case "rchar":
- stats.ReadBytes = value
- case "wchar":
- stats.WriteBytes = value
- }
- }
-}
diff --git a/metrics/disk_nop.go b/metrics/disk_nop.go
deleted file mode 100644
index 4319f8b27..000000000
--- a/metrics/disk_nop.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// +build !linux
-
-package metrics
-
-import "errors"
-
-// ReadDiskStats retrieves the disk IO stats belonging to the current process.
-func ReadDiskStats(stats *DiskStats) error {
- return errors.New("Not implemented")
-}
diff --git a/metrics/metrics.go b/metrics/metrics.go
deleted file mode 100644
index 9c6c3f434..000000000
--- a/metrics/metrics.go
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2015 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package metrics provides general system and process level metrics collection.
-package metrics
-
-import (
- "os"
- "runtime"
- "strings"
- "time"
-
- // "github.com/ethereum/go-ethereum/log"
- "github.com/rcrowley/go-metrics"
- "github.com/rcrowley/go-metrics/exp"
-)
-
-// MetricsEnabledFlag is the CLI flag name to use to enable metrics collections.
-const MetricsEnabledFlag = "metrics"
-const DashboardEnabledFlag = "dashboard"
-
-// Enabled is the flag specifying if metrics are enable or not.
-var Enabled = false
-
-// Init enables or disables the metrics system. Since we need this to run before
-// any other code gets to create meters and timers, we'll actually do an ugly hack
-// and peek into the command line args for the metrics flag.
-func init() {
- for _, arg := range os.Args {
- if flag := strings.TrimLeft(arg, "-"); flag == MetricsEnabledFlag || flag == DashboardEnabledFlag {
- // log.Info("Enabling metrics collection")
- Enabled = true
- }
- }
- exp.Exp(metrics.DefaultRegistry)
-}
-
-// NewCounter create a new metrics Counter, either a real one of a NOP stub depending
-// on the metrics flag.
-func NewCounter(name string) metrics.Counter {
- if !Enabled {
- return new(metrics.NilCounter)
- }
- return metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry)
-}
-
-// NewMeter create a new metrics Meter, either a real one of a NOP stub depending
-// on the metrics flag.
-func NewMeter(name string) metrics.Meter {
- if !Enabled {
- return new(metrics.NilMeter)
- }
- return metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry)
-}
-
-// NewTimer create a new metrics Timer, either a real one of a NOP stub depending
-// on the metrics flag.
-func NewTimer(name string) metrics.Timer {
- if !Enabled {
- return new(metrics.NilTimer)
- }
- return metrics.GetOrRegisterTimer(name, metrics.DefaultRegistry)
-}
-
-// CollectProcessMetrics periodically collects various metrics about the running
-// process.
-func CollectProcessMetrics(refresh time.Duration) {
- // Short circuit if the metrics system is disabled
- if !Enabled {
- return
- }
- // Create the various data collectors
- memstats := make([]*runtime.MemStats, 2)
- diskstats := make([]*DiskStats, 2)
- for i := 0; i < len(memstats); i++ {
- memstats[i] = new(runtime.MemStats)
- diskstats[i] = new(DiskStats)
- }
- // Define the various metrics to collect
- memAllocs := metrics.GetOrRegisterMeter("system/memory/allocs", metrics.DefaultRegistry)
- memFrees := metrics.GetOrRegisterMeter("system/memory/frees", metrics.DefaultRegistry)
- memInuse := metrics.GetOrRegisterMeter("system/memory/inuse", metrics.DefaultRegistry)
- memPauses := metrics.GetOrRegisterMeter("system/memory/pauses", metrics.DefaultRegistry)
-
- var diskReads, diskReadBytes, diskWrites, diskWriteBytes metrics.Meter
- if err := ReadDiskStats(diskstats[0]); err == nil {
- diskReads = metrics.GetOrRegisterMeter("system/disk/readcount", metrics.DefaultRegistry)
- diskReadBytes = metrics.GetOrRegisterMeter("system/disk/readdata", metrics.DefaultRegistry)
- diskWrites = metrics.GetOrRegisterMeter("system/disk/writecount", metrics.DefaultRegistry)
- diskWriteBytes = metrics.GetOrRegisterMeter("system/disk/writedata", metrics.DefaultRegistry)
- } else {
- // log.Debug("Failed to read disk metrics", "err", err)
- }
- // Iterate loading the different stats and updating the meters
- for i := 1; ; i++ {
- runtime.ReadMemStats(memstats[i%2])
- memAllocs.Mark(int64(memstats[i%2].Mallocs - memstats[(i-1)%2].Mallocs))
- memFrees.Mark(int64(memstats[i%2].Frees - memstats[(i-1)%2].Frees))
- memInuse.Mark(int64(memstats[i%2].Alloc - memstats[(i-1)%2].Alloc))
- memPauses.Mark(int64(memstats[i%2].PauseTotalNs - memstats[(i-1)%2].PauseTotalNs))
-
- if ReadDiskStats(diskstats[i%2]) == nil {
- diskReads.Mark(diskstats[i%2].ReadCount - diskstats[(i-1)%2].ReadCount)
- diskReadBytes.Mark(diskstats[i%2].ReadBytes - diskstats[(i-1)%2].ReadBytes)
- diskWrites.Mark(diskstats[i%2].WriteCount - diskstats[(i-1)%2].WriteCount)
- diskWriteBytes.Mark(diskstats[i%2].WriteBytes - diskstats[(i-1)%2].WriteBytes)
- }
- time.Sleep(refresh)
- }
-}
diff --git a/mintdb/database.go b/mintdb/database.go
index d804ce817..4a432f7aa 100644
--- a/mintdb/database.go
+++ b/mintdb/database.go
@@ -17,18 +17,13 @@
package mintdb
import (
- "strconv"
- "strings"
- "sync"
- "time"
-
- // "github.com/ethereum/go-ethereum/log"
- "github.com/MinterTeam/minter-go-node/metrics"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/filter"
"github.com/syndtr/goleveldb/leveldb/iterator"
"github.com/syndtr/goleveldb/leveldb/opt"
+ "sync"
+ "time"
gometrics "github.com/rcrowley/go-metrics"
)
@@ -173,108 +168,6 @@ func (db *LDBDatabase) LDB() *leveldb.DB {
return db.db
}
-// Meter configures the database metrics collectors and
-func (db *LDBDatabase) Meter(prefix string) {
- // Short circuit metering if the metrics system is disabled
- if !metrics.Enabled {
- return
- }
- // Initialize all the metrics collector at the requested prefix
- db.getTimer = metrics.NewTimer(prefix + "user/gets")
- db.putTimer = metrics.NewTimer(prefix + "user/puts")
- db.delTimer = metrics.NewTimer(prefix + "user/dels")
- db.missMeter = metrics.NewMeter(prefix + "user/misses")
- db.readMeter = metrics.NewMeter(prefix + "user/reads")
- db.writeMeter = metrics.NewMeter(prefix + "user/writes")
- db.compTimeMeter = metrics.NewMeter(prefix + "compact/time")
- db.compReadMeter = metrics.NewMeter(prefix + "compact/input")
- db.compWriteMeter = metrics.NewMeter(prefix + "compact/output")
-
- // Create a quit channel for the periodic collector and run it
- db.quitLock.Lock()
- db.quitChan = make(chan chan error)
- db.quitLock.Unlock()
-
- go db.meter(3 * time.Second)
-}
-
-// meter periodically retrieves internal leveldb counters and reports them to
-// the metrics subsystem.
-//
-// This is how a stats table look like (currently):
-// Compactions
-// Level | Tables | Size(MB) | Time(sec) | Read(MB) | Write(MB)
-// -------+------------+---------------+---------------+---------------+---------------
-// 0 | 0 | 0.00000 | 1.27969 | 0.00000 | 12.31098
-// 1 | 85 | 109.27913 | 28.09293 | 213.92493 | 214.26294
-// 2 | 523 | 1000.37159 | 7.26059 | 66.86342 | 66.77884
-// 3 | 570 | 1113.18458 | 0.00000 | 0.00000 | 0.00000
-func (db *LDBDatabase) meter(refresh time.Duration) {
- // Create the counters to store current and previous values
- counters := make([][]float64, 2)
- for i := 0; i < 2; i++ {
- counters[i] = make([]float64, 3)
- }
- // Iterate ad infinitum and collect the stats
- for i := 1; ; i++ {
- // Retrieve the database stats
- stats, err := db.db.GetProperty("leveldb.stats")
- if err != nil {
- // db.log.Error("Failed to read database stats", "err", err)
- return
- }
- // Find the compaction table, skip the header
- lines := strings.Split(stats, "\n")
- for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" {
- lines = lines[1:]
- }
- if len(lines) <= 3 {
- // db.log.Error("Compaction table not found")
- return
- }
- lines = lines[3:]
-
- // Iterate over all the table rows, and accumulate the entries
- for j := 0; j < len(counters[i%2]); j++ {
- counters[i%2][j] = 0
- }
- for _, line := range lines {
- parts := strings.Split(line, "|")
- if len(parts) != 6 {
- break
- }
- for idx, counter := range parts[3:] {
- value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64)
- if err != nil {
- // db.log.Error("Compaction entry parsing failed", "err", err)
- return
- }
- counters[i%2][idx] += value
- }
- }
- // Update all the requested meters
- if db.compTimeMeter != nil {
- db.compTimeMeter.Mark(int64((counters[i%2][0] - counters[(i-1)%2][0]) * 1000 * 1000 * 1000))
- }
- if db.compReadMeter != nil {
- db.compReadMeter.Mark(int64((counters[i%2][1] - counters[(i-1)%2][1]) * 1024 * 1024))
- }
- if db.compWriteMeter != nil {
- db.compWriteMeter.Mark(int64((counters[i%2][2] - counters[(i-1)%2][2]) * 1024 * 1024))
- }
- // Sleep a bit, then repeat the stats collection
- select {
- case errc := <-db.quitChan:
- // Quit requesting, stop hammering the database
- errc <- nil
- return
-
- case <-time.After(refresh):
- // Timeout, gather a new set of stats
- }
- }
-}
-
func (db *LDBDatabase) NewBatch() Batch {
return &ldbBatch{db: db.db, b: new(leveldb.Batch)}
}
diff --git a/networks/testnet/config.toml b/networks/testnet/config.toml
index 5949f30f4..08fafd0e3 100644
--- a/networks/testnet/config.toml
+++ b/networks/testnet/config.toml
@@ -109,6 +109,7 @@ recheck = true
recheck_empty = false
broadcast = true
wal_dir = "data/mempool.wal"
+cache_size = 0
##### consensus configuration options #####
[consensus]
diff --git a/networks/testnet/genesis.json b/networks/testnet/genesis.json
index ea7c27040..cf75e8a27 100644
--- a/networks/testnet/genesis.json
+++ b/networks/testnet/genesis.json
@@ -1,6 +1,6 @@
{
"genesis_time": "2018-06-09T00:00:00Z",
- "chain_id": "minter-test-network-9",
+ "chain_id": "minter-test-network-10",
"validators": [
{
"pub_key": {
diff --git a/rlp/decode.go b/rlp/decode.go
index 60d9dab2b..dbbe59959 100644
--- a/rlp/decode.go
+++ b/rlp/decode.go
@@ -29,6 +29,23 @@ import (
)
var (
+ // EOL is returned when the end of the current list
+ // has been reached during streaming.
+ EOL = errors.New("rlp: end of list")
+
+ // Actual Errors
+ ErrExpectedString = errors.New("rlp: expected String or Byte")
+ ErrExpectedList = errors.New("rlp: expected List")
+ ErrCanonInt = errors.New("rlp: non-canonical integer format")
+ ErrCanonSize = errors.New("rlp: non-canonical size information")
+ ErrElemTooLarge = errors.New("rlp: element is larger than containing list")
+ ErrValueTooLarge = errors.New("rlp: value size exceeds available input length")
+ ErrMoreThanOneValue = errors.New("rlp: input contains more than one value")
+
+ // internal errors
+ errNotInList = errors.New("rlp: call of ListEnd outside of any list")
+ errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL")
+ errUintOverflow = errors.New("rlp: uint overflow")
errNoPointer = errors.New("rlp: interface given to Decode must be a pointer")
errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
)
@@ -274,9 +291,8 @@ func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) {
if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) {
if typ.Kind() == reflect.Array {
return decodeByteArray, nil
- } else {
- return decodeByteSlice, nil
}
+ return decodeByteSlice, nil
}
etypeinfo, err := cachedTypeInfo1(etype, tags{})
if err != nil {
@@ -555,29 +571,6 @@ func (k Kind) String() string {
}
}
-var (
- // EOL is returned when the end of the current list
- // has been reached during streaming.
- EOL = errors.New("rlp: end of list")
-
- // Actual Errors
- ErrExpectedString = errors.New("rlp: expected String or Byte")
- ErrExpectedList = errors.New("rlp: expected List")
- ErrCanonInt = errors.New("rlp: non-canonical integer format")
- ErrCanonSize = errors.New("rlp: non-canonical size information")
- ErrElemTooLarge = errors.New("rlp: element is larger than containing list")
- ErrValueTooLarge = errors.New("rlp: value size exceeds available input length")
-
- // This error is reported by DecodeBytes if the slice contains
- // additional data after the first RLP value.
- ErrMoreThanOneValue = errors.New("rlp: input contains more than one value")
-
- // internal errors
- errNotInList = errors.New("rlp: call of ListEnd outside of any list")
- errNotAtEOL = errors.New("rlp: call of ListEnd not positioned at EOL")
- errUintOverflow = errors.New("rlp: uint overflow")
-)
-
// ByteReader must be implemented by any input reader for a Stream. It
// is implemented by e.g. bufio.Reader and bytes.Reader.
type ByteReader interface {
diff --git a/rlp/encode.go b/rlp/encode.go
index 44592c2f5..445b4b5b2 100644
--- a/rlp/encode.go
+++ b/rlp/encode.go
@@ -92,7 +92,7 @@ func Encode(w io.Writer, val interface{}) error {
return eb.toWriter(w)
}
-// EncodeBytes returns the RLP encoding of val.
+// EncodeToBytes returns the RLP encoding of val.
// Please see the documentation of Encode for the encoding rules.
func EncodeToBytes(val interface{}) ([]byte, error) {
eb := encbufPool.Get().(*encbuf)
@@ -104,7 +104,7 @@ func EncodeToBytes(val interface{}) ([]byte, error) {
return eb.toBytes(), nil
}
-// EncodeReader returns a reader from which the RLP encoding of val
+// EncodeToReader returns a reader from which the RLP encoding of val
// can be read. The returned size is the total size of the encoded
// data.
//
@@ -151,11 +151,10 @@ func puthead(buf []byte, smalltag, largetag byte, size uint64) int {
if size < 56 {
buf[0] = smalltag + byte(size)
return 1
- } else {
- sizesize := putint(buf[1:], size)
- buf[0] = largetag + byte(sizesize)
- return sizesize + 1
}
+ sizesize := putint(buf[1:], size)
+ buf[0] = largetag + byte(sizesize)
+ return sizesize + 1
}
// encbufs are pooled.
@@ -218,7 +217,7 @@ func (w *encbuf) list() *listhead {
func (w *encbuf) listEnd(lh *listhead) {
lh.size = w.size() - lh.offset - lh.size
if lh.size < 56 {
- w.lhsize += 1 // length encoded into kind tag
+ w.lhsize++ // length encoded into kind tag
} else {
w.lhsize += 1 + intsize(uint64(lh.size))
}
@@ -322,10 +321,9 @@ func (r *encReader) next() []byte {
p := r.buf.str[r.strpos:head.offset]
r.strpos += sizebefore
return p
- } else {
- r.lhpos++
- return head.encode(r.buf.sizebuf)
}
+ r.lhpos++
+ return head.encode(r.buf.sizebuf)
case r.strpos < len(r.buf.str):
// String data at the end, after all list headers.
@@ -576,9 +574,8 @@ func makePtrWriter(typ reflect.Type) (writer, error) {
writer := func(val reflect.Value, w *encbuf) error {
if val.IsNil() {
return nilfunc(w)
- } else {
- return etypeinfo.writer(val.Elem(), w)
}
+ return etypeinfo.writer(val.Elem(), w)
}
return writer, err
}
diff --git a/version/version.go b/version/version.go
index 9691804c7..7c1c78936 100755
--- a/version/version.go
+++ b/version/version.go
@@ -4,12 +4,12 @@ package version
const (
Maj = "0"
Min = "0"
- Fix = "5"
+ Fix = "6"
)
var (
// Must be a string because scripts like dist.sh read this file.
- Version = "0.0.5"
+ Version = "0.0.6"
// GitCommit is the current HEAD set using ldflags.
GitCommit string