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