diff --git a/CHANGELOG.md b/CHANGELOG.md index a2d317443..c69a23e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## TBD + +- [api] Add validators rewards to block api + +## 0.1.0 +*Jule 23th, 2018* + +BREAKING CHANGES + +- [core] 0.1x transaction fees +- [core] Genesis is now encapsulated in code +- [core] Add new transaction type: SellAllCoin +- [core] Add GasCoin field to transaction +- [config] New config directories +- [api] Huge API update. For more info see docs + +IMPROVEMENT + +- [binary] Now Minter is available as single binary. There is no need to install Tendermint +- [config] 10x default send/recv rate +- [config] Recheck after empty blocks +- [core] Check transaction nonce before adding to mempool +- [performance] Huge performance enhancement due to getting rid of network overhead between tendermint and minter +- [gui] GUI introduced! You can use it by visiting http://localhost:3000/ in your local browser + +BUG FIXES + +- [api] Fixed raw transaction output + ## 0.0.6 *Jule 16th, 2018* diff --git a/Gopkg.lock b/Gopkg.lock index 6bf7d4c34..43a283c11 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "github.com/btcsuite/btcd" packages = ["btcec"] - revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" + revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898" [[projects]] name = "github.com/davecgh/go-spew" @@ -24,17 +24,6 @@ packages = ["."] revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" -[[projects]] - name = "github.com/ethereum/go-ethereum" - packages = [ - "common", - "common/hexutil", - "common/math", - "crypto/sha3" - ] - revision = "dea1ce052a10cd7d401a5c04f83f371a06fe293c" - version = "v1.8.11" - [[projects]] name = "github.com/go-kit/kit" packages = [ @@ -61,6 +50,12 @@ revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" version = "v1.7.0" +[[projects]] + name = "github.com/gobuffalo/packr" + packages = ["."] + revision = "bd47f2894846e32edcf9aa37290fef76c327883f" + version = "v1.11.1" + [[projects]] name = "github.com/gogo/protobuf" packages = [ @@ -131,6 +126,18 @@ packages = ["."] revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" +[[projects]] + name = "github.com/kr/pretty" + packages = ["."] + revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712" + version = "v0.1.0" + +[[projects]] + name = "github.com/kr/text" + packages = ["."] + revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f" + version = "v0.1.0" + [[projects]] name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] @@ -176,7 +183,7 @@ "nfs", "xfs" ] - revision = "40f013a808ec4fa79def444a1a56de4d1727efcb" + revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] name = "github.com/rcrowley/go-metrics" @@ -206,7 +213,7 @@ "leveldb/table", "leveldb/util" ] - revision = "5d6fca44a948d2be89a9702de7717f0168403d3d" + revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] branch = "master" @@ -230,7 +237,6 @@ "abci/client", "abci/example/code", "abci/example/kvstore", - "abci/server", "abci/types", "blockchain", "config", @@ -241,6 +247,7 @@ "crypto/tmhash", "evidence", "libs/autofile", + "libs/cli/flags", "libs/clist", "libs/common", "libs/db", @@ -280,6 +287,7 @@ name = "golang.org/x/crypto" packages = [ "curve25519", + "internal/subtle", "nacl/box", "nacl/secretbox", "openpgp/armor", @@ -288,7 +296,7 @@ "ripemd160", "salsa20/salsa" ] - revision = "b47b1587369238182299fe4dad77d05b8b461e06" + revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" [[projects]] name = "golang.org/x/net" @@ -365,7 +373,7 @@ branch = "v1" name = "gopkg.in/check.v1" packages = ["."] - revision = "20d25e2804050c1cd24a7eea1e7a6447dd0e74ec" + revision = "788fd78401277ebd861206a03c884797c6ec5541" [[projects]] branch = "v2" @@ -376,6 +384,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "fe1d4dfba2b86ce6861297f5a51daef78af4bd3489b827d917f19b8f0986e66c" + inputs-digest = "23202c1428a7bc0a7a1946e1c160b380ef49332be93b6b091750e567f0bea609" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 5cf3a7ff0..06c0d48ad 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -24,10 +24,15 @@ # go-tests = true # unused-packages = true + [[constraint]] branch = "master" name = "github.com/btcsuite/btcd" +[[constraint]] + name = "github.com/gobuffalo/packr" + version = "1.11.1" + [[constraint]] name = "github.com/gorilla/mux" version = "1.6.2" @@ -52,10 +57,18 @@ branch = "master" name = "github.com/syndtr/goleveldb" +[[constraint]] + name = "github.com/tendermint/go-amino" + version = "0.10.1" + [[constraint]] name = "github.com/tendermint/tendermint" version = "0.22.4" +[[constraint]] + branch = "v1" + name = "gopkg.in/check.v1" + [[constraint]] branch = "v2" name = "gopkg.in/karalabe/cookiejar.v2" diff --git a/Makefile b/Makefile index 5005b327b..a7615a389 100644 --- a/Makefile +++ b/Makefile @@ -106,17 +106,4 @@ build-linux: GOOS=linux GOARCH=amd64 $(MAKE) build build-compress: - upx --brute -9 build/minter - -build-docker-localnode: - cd networks/local - make - -# Run a 4-node testnet locally -localnet-start: localnet-stop - @if ! [ -f build/node0/config/genesis.json ]; then docker run --rm -v $(CURDIR)/build:/minter:Z minter/localnode testnet --v 4 --o . --populate-persistent-peers --starting-ip-address 192.167.10.2 ; fi - docker-compose up - -# Stop testnet -localnet-stop: - docker-compose down + upx --brute -9 build/minter \ No newline at end of file diff --git a/README.md b/README.md index 3bc556b21..da6534834 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Minter is a blockchain network that lets people, projects, and companies issue a [![license](https://img.shields.io/github/license/MinterTeam/minter-go-node.svg)](https://github.com/MinterTeam/minter-go-node/blob/master/LICENSE) [![last-commit](https://img.shields.io/github/last-commit/MinterTeam/minter-go-node.svg)](https://github.com/MinterTeam/minter-go-node/commits/master) [![Documentation Status](//readthedocs.org/projects/minter-go-node/badge/?version=latest)](https://minter-go-node.readthedocs.io/en/latest/?badge=latest) +[![Go Report Card](https://goreportcard.com/badge/github.com/MinterTeam/minter-go-node)](https://goreportcard.com/report/github.com/MinterTeam/minter-go-node) _NOTE: This is alpha software. Please contact us if you intend to run it in production._ @@ -33,7 +34,7 @@ interfaces exposed to other processes, but does not include the in-process Go AP 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 +will work with existing blockchain. In these cases you will have to start a new blockchain, or write something custom to get the old data into the new chain. diff --git a/api/api.go b/api/api.go index 19f6708ad..d36218b62 100644 --- a/api/api.go +++ b/api/api.go @@ -10,18 +10,26 @@ import ( "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/core/minter" "github.com/MinterTeam/minter-go-node/core/state" + "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/node" rpc "github.com/tendermint/tendermint/rpc/client" "strconv" "time" ) var ( + cdc = amino.NewCodec() blockchain *minter.Blockchain - client *rpc.HTTP + client *rpc.Local ) -func RunApi(b *minter.Blockchain) { - client = rpc.NewHTTP(*utils.TendermintRpcAddrFlag, "/websocket") +func init() { + crypto.RegisterAmino(cdc) +} + +func RunApi(b *minter.Blockchain, node *node.Node) { + client = rpc.NewLocal(node) blockchain = b @@ -40,6 +48,7 @@ func RunApi(b *minter.Blockchain) { router.HandleFunc("/api/block/{height}", Block).Methods("GET") router.HandleFunc("/api/transactions", Transactions).Methods("GET") router.HandleFunc("/api/status", Status).Methods("GET") + router.HandleFunc("/api/net_info", NetInfo).Methods("GET") router.HandleFunc("/api/coinInfo/{symbol}", GetCoinInfo).Methods("GET") router.HandleFunc("/api/estimateCoinSell", EstimateCoinSell).Methods("GET") router.HandleFunc("/api/estimateCoinBuy", EstimateCoinBuy).Methods("GET") @@ -53,7 +62,13 @@ func RunApi(b *minter.Blockchain) { handler := c.Handler(router) // wait for tendermint to start - for true { + waitForTendermint() + + log.Fatal(http.ListenAndServe(*utils.MinterAPIAddrFlag, handler)) +} + +func waitForTendermint() { + for { _, err := client.Health() if err == nil { break @@ -61,8 +76,6 @@ func RunApi(b *minter.Blockchain) { time.Sleep(1 * time.Second) } - - log.Fatal(http.ListenAndServe(*utils.MinterAPIAddrFlag, handler)) } type Response struct { diff --git a/api/balance.go b/api/balance.go index 8bf56bf96..ece356c64 100644 --- a/api/balance.go +++ b/api/balance.go @@ -7,7 +7,9 @@ import ( "net/http" ) -type BalanceResponse map[string]string +type BalanceResponse struct { + Balance map[string]string `json:"balance"` +} type BalanceRequest struct { Address types.Address `json:"address"` @@ -21,15 +23,17 @@ func GetBalance(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) address := types.HexToAddress(vars["address"]) - balance := BalanceResponse{} + balanceResponse := BalanceResponse{ + Balance: make(map[string]string), + } balances := cState.GetBalances(address) for k, v := range balances.Data { - balance[k.String()] = v.String() + balanceResponse.Balance[k.String()] = v.String() } - if _, exists := balance[types.GetBaseCoin().String()]; !exists { - balance[types.GetBaseCoin().String()] = "0" + if _, exists := balanceResponse.Balance[types.GetBaseCoin().String()]; !exists { + balanceResponse.Balance[types.GetBaseCoin().String()] = "0" } w.Header().Set("Content-Type", "application/json; charset=UTF-8") @@ -37,6 +41,6 @@ func GetBalance(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(Response{ Code: 0, - Result: balance, + Result: balanceResponse, }) } diff --git a/api/balance_watcher.go b/api/balance_watcher.go index 754d3a8d6..14c54ef8a 100644 --- a/api/balance_watcher.go +++ b/api/balance_watcher.go @@ -1,9 +1,13 @@ package api import ( + "github.com/MinterTeam/minter-go-node/core/minter" "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/gorilla/websocket" "log" + "math/big" "net/http" ) @@ -26,6 +30,21 @@ func GetBalanceWatcher(w http.ResponseWriter, r *http.Request) { func handleBalanceChanges() { for { msg := <-state.BalanceChangeChan + + balanceInBasecoin := big.NewInt(0) + + if msg.Coin == types.GetBaseCoin() { + balanceInBasecoin = msg.Balance + } else { + sCoin := minter.GetBlockchain().CurrentState().GetStateCoin(msg.Coin) + + if sCoin != nil { + balanceInBasecoin = formula.CalculateSaleReturn(sCoin.Data().Volume, sCoin.Data().ReserveBalance, sCoin.Data().Crr, msg.Balance) + } + } + + msg.BalanceInBasecoin = balanceInBasecoin + for client := range clients { err := client.WriteJSON(msg) if err != nil { diff --git a/api/block.go b/api/block.go index c8761738d..c2288a81f 100644 --- a/api/block.go +++ b/api/block.go @@ -4,9 +4,9 @@ import ( "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" "github.com/gorilla/mux" "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/types" "math/big" "net/http" "strconv" @@ -20,7 +20,7 @@ type BlockResponse struct { NumTxs int64 `json:"num_txs"` TotalTxs int64 `json:"total_txs"` Transactions []BlockTransactionResponse `json:"transactions"` - Precommits []*types.Vote `json:"precommits"` + Precommits json.RawMessage `json:"precommits"` } type BlockTransactionResponse struct { @@ -34,6 +34,7 @@ type BlockTransactionResponse struct { Payload []byte `json:"payload"` ServiceData []byte `json:"service_data"` Gas int64 `json:"gas"` + GasCoin types.CoinSymbol `json:"gas_coin"` TxResult ResponseDeliverTx `json:"tx_result"` } @@ -65,8 +66,8 @@ func Block(w http.ResponseWriter, r *http.Request) { sender, _ := tx.Sender() txs[i] = BlockTransactionResponse{ - Hash: fmt.Sprintf("Mt%x", types.Tx(rawTx).Hash()), - RawTx: fmt.Sprintf("%x", rawTx), + Hash: fmt.Sprintf("Mt%x", rawTx.Hash()), + RawTx: fmt.Sprintf("%x", []byte(rawTx)), From: sender.String(), Nonce: tx.Nonce, GasPrice: tx.GasPrice, @@ -75,6 +76,7 @@ func Block(w http.ResponseWriter, r *http.Request) { Payload: tx.Payload, ServiceData: tx.ServiceData, Gas: tx.Gas(), + GasCoin: tx.GasCoin, TxResult: ResponseDeliverTx{ Code: blockResults.Results.DeliverTx[i].Code, Data: blockResults.Results.DeliverTx[i].Data, @@ -87,13 +89,15 @@ func Block(w http.ResponseWriter, r *http.Request) { } } + precommits, _ := cdc.MarshalJSON(block.Block.LastCommit.Precommits) + response := BlockResponse{ 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, + Precommits: json.RawMessage(precommits), Transactions: txs, } diff --git a/api/estimate_coin_buy.go b/api/estimate_coin_buy.go index 8da521323..395144157 100644 --- a/api/estimate_coin_buy.go +++ b/api/estimate_coin_buy.go @@ -4,12 +4,19 @@ 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/transaction" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "math/big" "net/http" ) +type EstimateCoinBuyResponse struct { + WillPay string `json:"will_pay"` + Commission string `json:"commission"` +} + func EstimateCoinBuy(w http.ResponseWriter, r *http.Request) { cState := GetStateForRequest(r) @@ -56,6 +63,24 @@ func EstimateCoinBuy(w http.ResponseWriter, r *http.Request) { return } + commissionInBaseCoin := big.NewInt(commissions.ConvertTx) + commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if coinToSellSymbol != types.GetBaseCoin() { + coin := cState.GetStateCoin(coinToSellSymbol) + + if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(Response{ + Code: 1, + 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) + } + if coinToSellSymbol == types.GetBaseCoin() { coin := cState.GetStateCoin(coinToBuySymbol).Data() result = formula.CalculatePurchaseAmount(coin.Volume, coin.ReserveBalance, coin.Crr, valueToBuy) @@ -71,7 +96,10 @@ func EstimateCoinBuy(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: 0, - Result: result.String(), + Code: 0, + Result: EstimateCoinBuyResponse{ + WillPay: result.String(), + Commission: commission.String(), + }, }) } diff --git a/api/estimate_coin_sell.go b/api/estimate_coin_sell.go index 75d2ce8df..ff08e4845 100644 --- a/api/estimate_coin_sell.go +++ b/api/estimate_coin_sell.go @@ -4,12 +4,19 @@ 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/transaction" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "math/big" "net/http" ) +type EstimateCoinSellResponse struct { + WillGet string `json:"will_get"` + Commission string `json:"commission"` +} + func EstimateCoinSell(w http.ResponseWriter, r *http.Request) { cState := GetStateForRequest(r) @@ -56,6 +63,24 @@ func EstimateCoinSell(w http.ResponseWriter, r *http.Request) { return } + commissionInBaseCoin := big.NewInt(commissions.ConvertTx) + commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if coinToSellSymbol != types.GetBaseCoin() { + coin := cState.GetStateCoin(coinToSellSymbol) + + if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(Response{ + Code: 1, + 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) + } + if coinToSellSymbol == types.GetBaseCoin() { coin := cState.GetStateCoin(coinToBuySymbol).Data() result = formula.CalculatePurchaseReturn(coin.Volume, coin.ReserveBalance, coin.Crr, valueToSell) @@ -71,7 +96,10 @@ func EstimateCoinSell(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: 0, - Result: result.String(), + Code: 0, + Result: EstimateCoinSellResponse{ + WillGet: result.String(), + Commission: commission.String(), + }, }) } diff --git a/api/get_bip_volume.go b/api/get_bip_volume.go index 3813e09f6..326a9da7e 100644 --- a/api/get_bip_volume.go +++ b/api/get_bip_volume.go @@ -9,6 +9,10 @@ import ( "strconv" ) +type BipVolumeResult struct { + Volume string `json:"volume"` +} + func GetBipVolume(w http.ResponseWriter, r *http.Request) { height, _ := strconv.Atoi(r.URL.Query().Get("height")) @@ -27,8 +31,10 @@ func GetBipVolume(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: 0, - Result: CalcBipVolume(height).String(), + Code: 0, + Result: BipVolumeResult{ + Volume: CalcBipVolume(height).String(), + }, }) } diff --git a/api/net_info.go b/api/net_info.go new file mode 100644 index 000000000..c3bb8b66a --- /dev/null +++ b/api/net_info.go @@ -0,0 +1,28 @@ +package api + +import ( + "encoding/json" + "net/http" +) + +func NetInfo(w http.ResponseWriter, r *http.Request) { + + result, err := client.NetInfo() + + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + + if err != nil { + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(Response{ + Code: 500, + Result: nil, + Log: err.Error(), + }) + return + } + + json.NewEncoder(w).Encode(Response{ + Code: 0, + Result: result, + }) +} diff --git a/api/send_transaction.go b/api/send_transaction.go index 83da7052c..d1c38ec75 100644 --- a/api/send_transaction.go +++ b/api/send_transaction.go @@ -15,6 +15,10 @@ type SendTransactionRequest struct { Transaction string `json:"transaction"` } +type SendTransactionResponse struct { + Hash string `json:"hash"` +} + func SendTransaction(w http.ResponseWriter, r *http.Request) { var req SendTransactionRequest @@ -52,7 +56,9 @@ func SendTransaction(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: code.OK, - Result: "Mt" + strings.ToLower(result.Hash.String()), + Code: code.OK, + Result: SendTransactionResponse{ + Hash: "Mt" + strings.ToLower(result.Hash.String()), + }, }) } diff --git a/api/send_transaction_async.go b/api/send_transaction_async.go index e88c9d957..1ddbd3b12 100644 --- a/api/send_transaction_async.go +++ b/api/send_transaction_async.go @@ -27,7 +27,9 @@ func SendTransactionAsync(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: code.OK, - Result: "Mt" + strings.ToLower(result.Hash.String()), + Code: code.OK, + Result: SendTransactionResponse{ + Hash: "Mt" + strings.ToLower(result.Hash.String()), + }, }) } diff --git a/api/send_transaction_sync.go b/api/send_transaction_sync.go index 15d90e2bc..4377d3c44 100644 --- a/api/send_transaction_sync.go +++ b/api/send_transaction_sync.go @@ -38,7 +38,9 @@ func SendTransactionSync(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: code.OK, - Result: "Mt" + strings.ToLower(result.Hash.String()), + Code: code.OK, + Result: SendTransactionResponse{ + Hash: "Mt" + strings.ToLower(result.Hash.String()), + }, }) } diff --git a/api/status.go b/api/status.go index c2334b7d8..86f740c45 100644 --- a/api/status.go +++ b/api/status.go @@ -2,16 +2,19 @@ package api import ( "encoding/json" + "github.com/MinterTeam/minter-go-node/version" "github.com/tendermint/tendermint/libs/common" "net/http" "time" ) type StatusResponse struct { + MinterVersion string `json:"version"` LatestBlockHash common.HexBytes `json:"latest_block_hash"` LatestAppHash common.HexBytes `json:"latest_app_hash"` LatestBlockHeight int64 `json:"latest_block_height"` LatestBlockTime time.Time `json:"latest_block_time"` + TmStatus json.RawMessage `json:"tm_status"` } func Status(w http.ResponseWriter, r *http.Request) { @@ -30,13 +33,17 @@ func Status(w http.ResponseWriter, r *http.Request) { return } + tmStatus, _ := cdc.MarshalJSON(result) + json.NewEncoder(w).Encode(Response{ Code: 0, Result: StatusResponse{ + MinterVersion: version.Version, LatestBlockHash: common.HexBytes(result.SyncInfo.LatestBlockHash), LatestAppHash: common.HexBytes(result.SyncInfo.LatestAppHash), LatestBlockHeight: result.SyncInfo.LatestBlockHeight, LatestBlockTime: result.SyncInfo.LatestBlockTime, + TmStatus: json.RawMessage(tmStatus), }, }) } diff --git a/api/transaction.go b/api/transaction.go index 43adabf1c..63ea45a9e 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -39,7 +39,7 @@ func Transaction(w http.ResponseWriter, r *http.Request) { Code: 0, Result: TransactionResponse{ Hash: common.HexBytes(tx.Tx.Hash()), - RawTx: fmt.Sprintf("%x", tx.Tx), + RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), Height: tx.Height, Index: tx.Index, TxResult: ResponseDeliverTx{ @@ -54,6 +54,7 @@ func Transaction(w http.ResponseWriter, r *http.Request) { From: sender.String(), Nonce: decodedTx.Nonce, GasPrice: decodedTx.GasPrice, + GasCoin: decodedTx.GasCoin, Type: decodedTx.Type, Data: decodedTx.GetDecodedData(), Payload: decodedTx.Payload, diff --git a/api/transaction_count.go b/api/transaction_count.go index 33f67538e..f1ca70736 100644 --- a/api/transaction_count.go +++ b/api/transaction_count.go @@ -7,7 +7,9 @@ import ( "net/http" ) -type TransactionCountResponse uint64 +type TransactionCountResponse struct { + Count uint64 `json:"count"` +} func GetTransactionCount(w http.ResponseWriter, r *http.Request) { @@ -20,7 +22,9 @@ func GetTransactionCount(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(Response{ - Code: 0, - Result: cState.GetNonce(address), + Code: 0, + Result: TransactionCountResponse{ + Count: cState.GetNonce(address), + }, }) } diff --git a/api/transactions.go b/api/transactions.go index 2a47e9ba1..4dba95f76 100644 --- a/api/transactions.go +++ b/api/transactions.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/rpc/core/types" "math/big" @@ -19,6 +20,7 @@ type TransactionResponse struct { From string `json:"from"` Nonce uint64 `json:"nonce"` GasPrice *big.Int `json:"gas_price"` + GasCoin types.CoinSymbol `json:"gas_coin"` Type byte `json:"type"` Data transaction.Data `json:"data"` Payload []byte `json:"payload"` @@ -67,7 +69,7 @@ func Transactions(w http.ResponseWriter, r *http.Request) { result[i] = TransactionResponse{ Hash: common.HexBytes(tx.Tx.Hash()), - RawTx: fmt.Sprintf("%x", tx.Tx), + RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), Height: tx.Height, Index: tx.Index, TxResult: ResponseDeliverTx{ @@ -82,6 +84,7 @@ func Transactions(w http.ResponseWriter, r *http.Request) { From: sender.String(), Nonce: decodedTx.Nonce, GasPrice: decodedTx.GasPrice, + GasCoin: decodedTx.GasCoin, Type: decodedTx.Type, Data: decodedTx.GetDecodedData(), Payload: decodedTx.Payload, diff --git a/cmd/minter/main-packr.go b/cmd/minter/main-packr.go new file mode 100644 index 000000000..c6d7f9070 --- /dev/null +++ b/cmd/minter/main-packr.go @@ -0,0 +1,11 @@ +// Code generated by github.com/gobuffalo/packr. DO NOT EDIT + +package main + +import "github.com/gobuffalo/packr" + +// You can use the "packr clean" command to clean up this, +// and any other packr generated files. +func init() { + packr.PackJSONBytes("./../../gui", "index.html", "\"PGh0bWw+CjxoZWFkPgogICAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogICAgPG1ldGEgbmFtZT0idmlld3BvcnQiCiAgICAgICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIHVzZXItc2NhbGFibGU9bm8sIGluaXRpYWwtc2NhbGU9MS4wLCBtYXhpbXVtLXNjYWxlPTEuMCwgbWluaW11bS1zY2FsZT0xLjAiPgogICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJpZT1lZGdlIj4KICAgIDx0aXRsZT5NaW50ZXIgTm9kZSBHVUk8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSJodHRwczovL3N0YWNrcGF0aC5ib290c3RyYXBjZG4uY29tL2Jvb3RzdHJhcC80LjEuMC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiCiAgICAgICAgICBpbnRlZ3JpdHk9InNoYTM4NC05Z1ZRNGRZRnd3V1NqSURabkxFV254Q2plU1dGcGhKaXdHUFhyMWpkZEloT2VnaXUxRndPNXFSR3ZGWE9kSlo0IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iaHR0cHM6Ly91c2UuZm9udGF3ZXNvbWUuY29tL3JlbGVhc2VzL3Y1LjEuMS9jc3MvYWxsLmNzcyIgaW50ZWdyaXR5PSJzaGEzODQtTzh3aFMzZmhHMk9uQTVLYXMwWTlsM2NmcG1ZamFwakkwRTR0aGVINGl1TUQrcExoYmY2SkkwaklNZlljSzN5WiIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS92dWUiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2F4aW9zLzAuMTguMC9heGlvcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2NyeXB0by1qcy8zLjEuOS0xL2NyeXB0by1qcy5taW4uanMiPjwvc2NyaXB0PgogICAgPHN0eWxlPgoKICAgICAgICAuY2FyZCB7CiAgICAgICAgICAgIG1hcmdpbi1ib3R0b206IDIwcHg7CiAgICAgICAgfQoKICAgICAgICBodG1sLGJvZHksLmJvZHkgewogICAgICAgICAgICBoZWlnaHQ6IDEwMCU7CiAgICAgICAgfQogICAgICAgIAogICAgICAgIC5ib2R5IHsKICAgICAgICAgICAgcGFkZGluZy10b3A6IDE1cHg7CiAgICAgICAgfQoKICAgICAgICAudGFibGUgewogICAgICAgICAgICBtYXJnaW4tYm90dG9tOiAwOwogICAgICAgICAgICB0YWJsZS1sYXlvdXQ6IGZpeGVkOwogICAgICAgIH0KCiAgICAgICAgLmNhcmQtaGVhZGVyIHsKICAgICAgICAgICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICAgICAgfQoKICAgICAgICAuY2FyZC1oZWFkZXIgewogICAgICAgICAgICBwYWRkaW5nLWxlZnQ6IDEycHg7CiAgICAgICAgfQoKICAgICAgICAuaCB7CiAgICAgICAgICAgIHdpZHRoOiAyMDBweDsKICAgICAgICAgICAgYmFja2dyb3VuZC1jb2xvcjogI2YzZjNmMzsKICAgICAgICAgICAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgI2NjYzsKICAgICAgICB9CgogICAgICAgIC5iZy1zdWNjZXNzLCAuYmctZGFuZ2VyIHsKICAgICAgICAgICAgY29sb3I6IHdoaXRlOwogICAgICAgIH0KCiAgICAgICAgLmJnLWRhbmdlciB7CiAgICAgICAgICAgIGJvcmRlci1jb2xvcjogI2RjMzU0NSAhaW1wb3J0YW50OwogICAgICAgIH0KCiAgICAgICAgLmJnLXN1Y2Nlc3MgewogICAgICAgICAgICBib3JkZXItY29sb3I6ICMyOGE3NDUgIWltcG9ydGFudDsKICAgICAgICB9CgogICAgICAgIC5mYS1jaGVjayB7CiAgICAgICAgICAgIGNvbG9yOiBncmVlbjsKICAgICAgICB9CgogICAgICAgIC5mYS1leGNsYW1hdGlvbi1jaXJjbGUgewogICAgICAgICAgICBjb2xvcjogcmVkOwogICAgICAgIH0KICAgIDwvc3R5bGU+CjwvaGVhZD4KPGJvZHkgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICMzNDNhNDAxYSI+CjxkaXYgaWQ9ImFwcCI+CiAgICA8bmF2IGNsYXNzPSJuYXZiYXIgbmF2YmFyLWV4cGFuZC1sZyBuYXZiYXItZGFyayBiZy1kYXJrIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLWJyYW5kIG1iLTAgaDEiPjxpIGNsYXNzPSJmYXMgZmEtdGVybWluYWwiPjwvaT4gJm5ic3A7IE1pbnRlciBGdWxsIE5vZGUgU3RhdHVzPC9zcGFuPgogICAgICAgIDwvZGl2PgogICAgPC9uYXY+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIgYm9keSIgdi1pZj0iZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWRhbmdlciIgcm9sZT0iYWxlcnQiPgogICAgICAgICAgICA8aDQgY2xhc3M9ImFsZXJ0LWhlYWRpbmciPkVycm9yIHdoaWxlIGNvbm5lY3RpbmcgdG8gbG9jYWwgbm9kZTwvaDQ+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0wIj57eyBlcnJvciB9fTwvcD4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGRpdiBjbGFzcz0iY29udGFpbmVyIGJvZHkgYmctd2hpdGUiIHYtaWY9InN0YXR1cyAmJiAhZXJyb3IiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIE5vZGUgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk1vbmlrZXI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubW9uaWtlciB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Tm9kZSBJRDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby5pZCB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TGlzdGVuIEFkZHI8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubGlzdGVuX2FkZHIgfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPk5ldHdvcmsgSUQ8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHN0YXR1cy5ub2RlX2luZm8ubmV0d29yayB9fTwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+TWludGVyIFZlcnNpb248L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPnt7IHZlcnNpb24gfX08L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPlRlbmRlcm1pbnQgVmVyc2lvbjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgc3RhdHVzLm5vZGVfaW5mby52ZXJzaW9uIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIiB2LWlmPSJuZXRfaW5mbyI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICBOZXQgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIExpc3RlbmluZzwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGkgOmNsYXNzPSJ7J2ZhLWNoZWNrJzogbmV0X2luZm8ubGlzdGVuaW5nfSIgY2xhc3M9ImZhcyI+PC9pPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaCI+Q29ubmVjdGVkIFBlZXJzPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD57eyBuZXRfaW5mby5uX3BlZXJzIH19IDxpIDpjbGFzcz0ieydmYS1leGNsYW1hdGlvbi1jaXJjbGUnOiBuZXRfaW5mby5uX3BlZXJzIDwgMX0iIGNsYXNzPSJmYXMiPjwvaT48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgICAgIFN5bmNpbmcgSW5mbwogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDx0YWJsZSBjbGFzcz0idGFibGUgY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImgiPklzIFN5bmNlZDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0ic3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCI+Tm88L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gdi1pZj0iIXN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXAiPlllczwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aSA6Y2xhc3M9InsnZmEtY2hlY2snOiAhc3RhdHVzLnN5bmNfaW5mby5jYXRjaGluZ191cCwgJ2ZhLWV4Y2xhbWF0aW9uLWNpcmNsZSc6IHN0YXR1cy5zeW5jX2luZm8uY2F0Y2hpbmdfdXB9IiBjbGFzcz0iZmFzIj48L2k+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJoIj5MYXRlc3QgQmxvY2s8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICN7eyBzdGF0dXMuc3luY19pbmZvLmxhdGVzdF9ibG9ja19oZWlnaHQgfX0gPHNwYW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9InRleHQtbXV0ZWQiPmF0IHt7IHN0YXR1cy5zeW5jX2luZm8ubGF0ZXN0X2Jsb2NrX3RpbWUgfX08L3NwYW4+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtaGVhZGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgVmFsaWRhdG9yIEluZm8KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8dGFibGUgY2xhc3M9InRhYmxlIGNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlB1YmxpYyBLZXk8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPk1we3sgYmFzZTY0VG9IZXgoc3RhdHVzLnZhbGlkYXRvcl9pbmZvLnB1Yl9rZXkudmFsdWUpIH19PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPlZvdGluZyBQb3dlcjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+e3sgbmljZU51bShzdGF0dXMudmFsaWRhdG9yX2luZm8udm90aW5nX3Bvd2VyKSB9fSA8c3BhbiBjbGFzcz0idGV4dC1tdXRlZCI+b2YgMTAwLDAwMCwwMDA8L3NwYW4+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CjwvZGl2Pgo8c2NyaXB0PgogICAgbmV3IFZ1ZSh7CiAgICAgICAgZWw6ICcjYXBwJywKICAgICAgICBkYXRhOiB7CiAgICAgICAgICAgIHN0YXR1czogbnVsbCwKICAgICAgICAgICAgdmVyc2lvbjogbnVsbCwKICAgICAgICAgICAgbmV0X2luZm86IG51bGwsCiAgICAgICAgICAgIGVycm9yOiBudWxsCiAgICAgICAgfSwKICAgICAgICBtb3VudGVkKCkgewogICAgICAgICAgICB0aGlzLnJlZnJlc2goKQoKICAgICAgICAgICAgc2V0SW50ZXJ2YWwodGhpcy5yZWZyZXNoLCAxMDAwKQogICAgICAgIH0sCiAgICAgICAgbWV0aG9kczogewogICAgICAgICAgICBuaWNlTnVtKG51bSkgewogICAgICAgICAgICAgICAgcmV0dXJuIG51bS50b1N0cmluZygpLnJlcGxhY2UoL1xCKD89KFxkezN9KSsoPyFcZCkpL2csICIsIikKICAgICAgICAgICAgfSwKICAgICAgICAgICAgYmFzZTY0VG9IZXgoYmFzZTY0KSB7CiAgICAgICAgICAgICAgICByZXR1cm4gQ3J5cHRvSlMuZW5jLkJhc2U2NC5wYXJzZShiYXNlNjQpLnRvU3RyaW5nKCkKICAgICAgICAgICAgfSwKICAgICAgICAgICAgcmVmcmVzaCgpIHsKICAgICAgICAgICAgICAgIGF4aW9zLmdldCgiLy8iICsgd2luZG93LmxvY2F0aW9uLmhvc3RuYW1lICsgJzo4ODQxL2FwaS9zdGF0dXMnKS50aGVuKGZ1bmN0aW9uIChkYXRhKSB7CiAgICAgICAgICAgICAgICAgICAgdGhpcy5zdGF0dXMgPSBkYXRhLmRhdGEucmVzdWx0LnRtX3N0YXR1cwogICAgICAgICAgICAgICAgICAgIHRoaXMudmVyc2lvbiA9IGRhdGEuZGF0YS5yZXN1bHQudmVyc2lvbgogICAgICAgICAgICAgICAgICAgIHRoaXMuZXJyb3IgPSBudWxsCiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpLmNhdGNoKGZ1bmN0aW9uIChyZWFzb24pIHsKICAgICAgICAgICAgICAgICAgICB0aGlzLmVycm9yID0gcmVhc29uLnRvU3RyaW5nKCk7CiAgICAgICAgICAgICAgICB9LmJpbmQodGhpcykpCgogICAgICAgICAgICAgICAgYXhpb3MuZ2V0KCIvLyIgKyB3aW5kb3cubG9jYXRpb24uaG9zdG5hbWUgKyAnOjg4NDEvYXBpL25ldF9pbmZvJykudGhlbihmdW5jdGlvbiAoZGF0YSkgewogICAgICAgICAgICAgICAgICAgIHRoaXMubmV0X2luZm8gPSBkYXRhLmRhdGEucmVzdWx0CiAgICAgICAgICAgICAgICAgICAgdGhpcy5lcnJvciA9IG51bGwKICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkuY2F0Y2goZnVuY3Rpb24gKHJlYXNvbikgewogICAgICAgICAgICAgICAgICAgIHRoaXMuZXJyb3IgPSByZWFzb24udG9TdHJpbmcoKTsKICAgICAgICAgICAgICAgIH0uYmluZCh0aGlzKSkKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0pCjwvc2NyaXB0Pgo8L2JvZHk+CjwvaHRtbD4=\"") +} diff --git a/cmd/minter/main.go b/cmd/minter/main.go index c6010303d..7cc957245 100644 --- a/cmd/minter/main.go +++ b/cmd/minter/main.go @@ -1,35 +1,83 @@ package main import ( + "fmt" "github.com/MinterTeam/minter-go-node/api" "github.com/MinterTeam/minter-go-node/cmd/utils" + "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/minter" + "github.com/MinterTeam/minter-go-node/genesis" "github.com/MinterTeam/minter-go-node/log" - "github.com/tendermint/tendermint/abci/server" + "github.com/gobuffalo/packr" "github.com/tendermint/tendermint/libs/common" + tmNode "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/types" + "net/http" + "os" ) func main() { - app := minter.NewMinterBlockchain() - // Start the listener - srv, err := server.NewServer(*utils.MinterAppAddrFlag, "socket", app) + err := common.EnsureDir(utils.GetMinterHome()+"/config", 0777) + if err != nil { - panic(err) - } - srv.SetLogger(log.With("module", "abci-server")) - if err := srv.Start(); err != nil { - panic(err) + log.Error(err.Error()) + os.Exit(1) } + app := minter.NewMinterBlockchain() + node := startTendermintNode(app) + + app.RunRPC(node) + + go runGUI() + if !*utils.DisableApi { - go api.RunApi(app) + go api.RunApi(app, node) } // Wait forever common.TrapSignal(func() { // Cleanup + node.Stop() app.Stop() - srv.Stop() }) } + +func runGUI() { + box := packr.NewBox("./../../gui") + + http.Handle("/", http.FileServer(box)) + log.Error(http.ListenAndServe(":3000", nil).Error()) +} + +func startTendermintNode(app *minter.Blockchain) *tmNode.Node { + + cfg := config.GetConfig() + + node, err := tmNode.NewNode( + cfg, + privval.LoadOrGenFilePV(cfg.PrivValidatorFile()), + proxy.NewLocalClientCreator(app), + func() (*types.GenesisDoc, error) { + return genesis.GetTestnetGenesis(), nil + }, + tmNode.DefaultDBProvider, + tmNode.DefaultMetricsProvider, + log.With("module", "tendermint"), + ) + + if err != nil { + fmt.Errorf("Failed to create a node: %v", err) + } + + if err = node.Start(); err != nil { + fmt.Errorf("Failed to start node: %v", err) + } + + log.Info("Started node", "nodeInfo", node.Switch().NodeInfo()) + + return node +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a4140091f..ba7c2d9db 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -7,11 +7,9 @@ import ( ) var ( - TendermintRpcAddrFlag = flag.String("tendermint_addr", "tcp://0.0.0.0:46657", "This is the address that minter will use to connect to the tendermint core node. Please provide a port.") - 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, "") + 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/config/config.go b/config/config.go new file mode 100644 index 000000000..5b559151a --- /dev/null +++ b/config/config.go @@ -0,0 +1,49 @@ +package config + +import ( + "github.com/MinterTeam/minter-go-node/cmd/utils" + tmConfig "github.com/tendermint/tendermint/config" +) + +var MinterDir = utils.GetMinterHome() +var config = tmConfig.DefaultConfig() + +func GetConfig() *tmConfig.Config { + + config.P2P.PersistentPeers = "249c62818bf4601605a65b5adc35278236bd5312@95.216.148.138:26656" + + config.Moniker = "MinterNode" + + config.TxIndex = &tmConfig.TxIndexConfig{ + Indexer: "kv", + IndexTags: "", + IndexAllTags: true, + } + + config.DBPath = MinterDir + "/tmdata" + + config.Mempool.CacheSize = 100000 + config.Mempool.WalPath = MinterDir + "/tmdata/mempool.wal" + config.Mempool.Recheck = true + config.Mempool.RecheckEmpty = true + + config.Consensus.WalPath = MinterDir + "/tmdata/cs.wal/wal" + config.Consensus.TimeoutPropose = 3000 + config.Consensus.TimeoutProposeDelta = 500 + config.Consensus.TimeoutPrevote = 1000 + config.Consensus.TimeoutPrevoteDelta = 500 + config.Consensus.TimeoutPrecommit = 1000 + config.Consensus.TimeoutPrecommitDelta = 500 + config.Consensus.TimeoutCommit = 5000 + + config.PrivValidator = MinterDir + "/config/priv_validator.json" + + config.NodeKey = MinterDir + "/config/node_key.json" + + config.P2P.AddrBook = MinterDir + "/config/addrbook.json" + config.P2P.ListenAddress = "tcp://0.0.0.0:26656" + config.P2P.SendRate = 5120000 // 5mb/s + config.P2P.RecvRate = 5120000 // 5mb/s + + return config +} diff --git a/core/code/code.go b/core/code/code.go index e0c5df7cf..24a4004fb 100644 --- a/core/code/code.go +++ b/core/code/code.go @@ -6,11 +6,9 @@ const ( WrongNonce uint32 = 101 CoinNotExists uint32 = 102 CoinReserveNotSufficient uint32 = 103 - TooLongPayload uint32 = 104 TxTooLarge uint32 = 105 DecodeError uint32 = 106 InsufficientFunds uint32 = 107 - UnknownTransactionType uint32 = 108 TxPayloadTooLarge uint32 = 109 TxServiceDataTooLarge uint32 = 110 @@ -37,4 +35,5 @@ const ( CheckExpired uint32 = 502 CheckUsed uint32 = 503 TooHighGasPrice uint32 = 504 + WrongGasCoin uint32 = 505 ) diff --git a/core/commissions/commissions.go b/core/commissions/commissions.go index 13ce845ff..8ce4de0c4 100644 --- a/core/commissions/commissions.go +++ b/core/commissions/commissions.go @@ -1,15 +1,14 @@ package commissions // all commissions are divided by 10^15 -// actual commission is SendTx * 10^15 = 1 000 000 000 000 000 PIP = 0,001 BIP +// actual commission is SendTx * 10^15 = 10 000 000 000 000 000 PIP = 0,01 BIP const ( SendTx int64 = 10 - ConvertTx int64 = 1000 + ConvertTx int64 = 100 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 2a21b0856..0354a65c1 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -14,6 +14,7 @@ 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/node" rpc "github.com/tendermint/tendermint/rpc/client" "math/big" ) @@ -29,7 +30,7 @@ type Blockchain struct { rewards *big.Int activeValidators abciTypes.Validators validatorsStatuses map[string]int8 - tendermintRPC *rpc.HTTP + tendermintRPC *rpc.Local BaseCoin types.CoinSymbol } @@ -58,9 +59,8 @@ func NewMinterBlockchain() *Blockchain { } blockchain = &Blockchain{ - db: db, - BaseCoin: types.GetBaseCoin(), - tendermintRPC: rpc.NewHTTP(*utils.TendermintRpcAddrFlag, "/websocket"), + db: db, + BaseCoin: types.GetBaseCoin(), } blockchain.updateCurrentRootHash() @@ -69,6 +69,10 @@ func NewMinterBlockchain() *Blockchain { return blockchain } +func (app *Blockchain) RunRPC(node *node.Node) { + app.tendermintRPC = rpc.NewLocal(node) +} + func (app *Blockchain) SetOption(req abciTypes.RequestSetOption) abciTypes.ResponseSetOption { return abciTypes.ResponseSetOption{} } @@ -201,7 +205,7 @@ func (app *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes.Respons for _, validator := range app.activeValidators { persisted := false for _, newValidator := range newValidators { - if bytes.Compare(validator.PubKey.Data, newValidator.PubKey.Data) == 0 { + if bytes.Equal(validator.PubKey.Data, newValidator.PubKey.Data) { persisted = true break } @@ -342,3 +346,7 @@ func (app *Blockchain) GetStateForHeight(height int) (*state.StateDB, error) { func (app *Blockchain) Height() uint64 { return app.height } + +func GetBlockchain() *Blockchain { + return blockchain +} diff --git a/core/state/balance_watcher.go b/core/state/balance_watcher.go index 00055fc57..af505357a 100644 --- a/core/state/balance_watcher.go +++ b/core/state/balance_watcher.go @@ -11,23 +11,30 @@ var ( ) type BalanceChangeStruct struct { - Address types.Address - Coin types.CoinSymbol - Balance *big.Int + Address types.Address + Coin types.CoinSymbol + Balance *big.Int + BalanceInBasecoin *big.Int } func (s BalanceChangeStruct) MarshalJSON() ([]byte, error) { return json.Marshal(struct { - Address types.Address `json:"address"` - Coin string `json:"coin"` - Balance string `json:"balance"` + Address types.Address `json:"address"` + Coin string `json:"coin"` + Balance string `json:"balance"` + BalanceInBasecoin string `json:"balance_in_basecoin"` }{ - Address: s.Address, - Coin: s.Coin.String(), - Balance: s.Balance.String(), + Address: s.Address, + Coin: s.Coin.String(), + Balance: s.Balance.String(), + BalanceInBasecoin: s.BalanceInBasecoin.String(), }) } func EmitBalanceChange(address types.Address, coin types.CoinSymbol, balance *big.Int) { - BalanceChangeChan <- BalanceChangeStruct{Address: address, Coin: coin, Balance: balance} + BalanceChangeChan <- BalanceChangeStruct{ + Address: address, + Coin: coin, + Balance: balance, + } } diff --git a/core/state/state_candidate.go b/core/state/state_candidate.go index 597d9b57a..f4f23b9d6 100644 --- a/core/state/state_candidate.go +++ b/core/state/state_candidate.go @@ -98,7 +98,7 @@ type Candidate struct { func (candidate Candidate) GetStakeOfAddress(addr types.Address, coin types.CoinSymbol) *Stake { for i, stake := range candidate.Stakes { - if bytes.Compare(stake.Coin.Bytes(), coin.Bytes()) == 0 && bytes.Compare(stake.Owner.Bytes(), addr.Bytes()) == 0 { + if bytes.Equal(stake.Coin.Bytes(), coin.Bytes()) && bytes.Equal(stake.Owner.Bytes(), addr.Bytes()) { return &(candidate.Stakes[i]) } } diff --git a/core/state/state_frozen_fund.go b/core/state/state_frozen_fund.go index 0a350e134..60c983c6c 100644 --- a/core/state/state_frozen_fund.go +++ b/core/state/state_frozen_fund.go @@ -123,7 +123,7 @@ func (c *stateFrozenFund) removeFund(candidateKey []byte) { for _, item := range c.data.List { // skip fund with given candidate key - if bytes.Compare(item.CandidateKey, candidateKey) == 0 { + if bytes.Equal(item.CandidateKey, candidateKey) { continue } diff --git a/core/state/statedb.go b/core/state/statedb.go index b3eaa9f0b..74aa57ba9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -590,11 +590,8 @@ func (s *StateDB) CoinExists(symbol types.CoinSymbol) bool { } stateCoin := s.getStateCoin(symbol) - if stateCoin != nil { - return true - } - return false + return stateCoin != nil } func (s *StateDB) CandidateExists(key types.Pubkey) bool { @@ -606,7 +603,7 @@ func (s *StateDB) CandidateExists(key types.Pubkey) bool { } for _, candidate := range stateCandidates.data { - if bytes.Compare(candidate.PubKey, key) == 0 { + if bytes.Equal(candidate.PubKey, key) { return true } } @@ -622,7 +619,7 @@ func (s *StateDB) GetStateCandidate(key types.Pubkey) *Candidate { } for i, candidate := range stateCandidates.data { - if bytes.Compare(candidate.PubKey, key) == 0 { + if bytes.Equal(candidate.PubKey, key) { return &(stateCandidates.data[i]) } } @@ -708,7 +705,7 @@ func (s *StateDB) AddAccumReward(pubkey types.Pubkey, reward *big.Int) { stateCandidates := s.getStateCandidates() for i := range stateCandidates.data { - if bytes.Compare(stateCandidates.data[i].PubKey, pubkey) == 0 { + if bytes.Equal(stateCandidates.data[i].PubKey, pubkey) { stateCandidates.data[i].AccumReward.Add(stateCandidates.data[i].AccumReward, reward) s.setStateCandidates(stateCandidates) s.MarkStateCandidateDirty() @@ -846,7 +843,7 @@ func (s *StateDB) SetCandidateOnline(pubkey []byte) { for i := range stateCandidates.data { candidate := &stateCandidates.data[i] - if bytes.Compare(candidate.PubKey, pubkey) == 0 { + if bytes.Equal(candidate.PubKey, pubkey) { candidate.Status = CandidateStatusOnline } } @@ -860,7 +857,7 @@ func (s *StateDB) SetCandidateOffline(pubkey []byte) { for i := range stateCandidates.data { candidate := &stateCandidates.data[i] - if bytes.Compare(candidate.PubKey, pubkey) == 0 { + if bytes.Equal(candidate.PubKey, pubkey) { candidate.Status = CandidateStatusOffline } } @@ -874,7 +871,7 @@ func (s *StateDB) SetValidatorAbsent(pubkey types.Pubkey) { for i := range stateCandidates.data { candidate := &stateCandidates.data[i] - if bytes.Compare(candidate.PubKey, pubkey) == 0 { + if bytes.Equal(candidate.PubKey, pubkey) { if candidate.Status == CandidateStatusOffline { return @@ -917,7 +914,7 @@ func (s *StateDB) PunishByzantineCandidate(PubKey []byte) { for i := range stateCandidates.data { candidate := &stateCandidates.data[i] - if bytes.Compare(candidate.PubKey, PubKey) == 0 { + if bytes.Equal(candidate.PubKey, PubKey) { candidate.AbsentTimes = candidate.AbsentTimes + 1 candidate.Stakes = []Stake{} @@ -948,7 +945,7 @@ func (s *StateDB) SetValidatorPresent(pubkey types.Pubkey) { for i := range stateCandidates.data { candidate := &stateCandidates.data[i] - if bytes.Compare(candidate.PubKey, pubkey) == 0 { + if bytes.Equal(candidate.PubKey, pubkey) { candidate.AbsentTimes = 0 } } diff --git a/core/transaction/buy_coin.go b/core/transaction/buy_coin.go index 1161f8e55..5f2af9af7 100644 --- a/core/transaction/buy_coin.go +++ b/core/transaction/buy_coin.go @@ -47,34 +47,46 @@ func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *stat Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin")} } + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + if !context.CoinExists(data.CoinToSell) { return Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists")} + Log: fmt.Sprintf("Coin %s not exists", data.CoinToSell)} } if !context.CoinExists(data.CoinToBuy) { return Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists")} + Log: fmt.Sprintf("Coin %s not exists", data.CoinToBuy)} } 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 tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) 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())} + Log: fmt.Sprintf("Gas coin reserve balance is not sufficient for transaction. Has: %s %s, required %s %s", coin.ReserveBalance().String(), types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin())} } commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin) } + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s.", sender.String(), commission.String(), tx.GasCoin)} + } + var value *big.Int if data.CoinToSell == types.GetBaseCoin() { @@ -82,11 +94,22 @@ func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *stat 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 { + if context.GetBalance(sender, data.CoinToSell).Cmp(value) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), value.String(), data.CoinToSell)} + } + + if data.CoinToSell == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, value) + totalTxCost.Add(totalTxCost, 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 %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !isCheck { @@ -103,7 +126,19 @@ func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *stat 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)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), data.CoinToSell)} + } + + if data.CoinToSell == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, value) + totalTxCost.Add(totalTxCost, 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 %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !isCheck { @@ -118,11 +153,22 @@ func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *stat 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 { + if context.GetBalance(sender, data.CoinToSell).Cmp(value) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), value.String(), data.CoinToSell)} + } + + if data.CoinToSell == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, value) + totalTxCost.Add(totalTxCost, 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 %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !isCheck { @@ -139,11 +185,11 @@ func (data BuyCoinData) Run(sender types.Address, tx *Transaction, context *stat if !isCheck { rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, data.CoinToSell, commission) + context.SubBalance(sender, tx.GasCoin, commission) - if data.CoinToSell != types.GetBaseCoin() { - context.SubCoinVolume(data.CoinToSell, commission) - context.SubCoinReserve(data.CoinToSell, commissionInBaseCoin) + if tx.GasCoin != types.GetBaseCoin() { + context.SubCoinVolume(tx.GasCoin, commission) + context.SubCoinReserve(tx.GasCoin, commissionInBaseCoin) } context.AddBalance(sender, data.CoinToBuy, value) diff --git a/core/transaction/create_coin.go b/core/transaction/create_coin.go index af2eb3c3d..8df7f2cb7 100644 --- a/core/transaction/create_coin.go +++ b/core/transaction/create_coin.go @@ -8,6 +8,7 @@ import ( "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" "regexp" @@ -50,6 +51,13 @@ func (data CreateCoinData) Gas() int64 { } func (data CreateCoinData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response { + + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + if len(data.Name) > maxCoinNameBytes { return Response{ Code: code.InvalidCoinName, @@ -62,8 +70,8 @@ func (data CreateCoinData) Run(sender types.Address, tx *Transaction, context *s 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) + commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas())) + commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) // compute additional price from letters count lettersCount := len(data.Symbol.String()) @@ -85,14 +93,43 @@ func (data CreateCoinData) Run(sender types.Address, tx *Transaction, context *s p := big.NewInt(10) p.Exp(p, big.NewInt(18), nil) p.Mul(p, big.NewInt(price)) - commission.Add(commission, p) + commissionInBaseCoin.Add(commissionInBaseCoin, p) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) + + if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { + return Response{ + Code: code.CoinReserveNotSufficient, + Log: fmt.Sprintf("Gas coin reserve balance is not sufficient for transaction. Has: %s %s, required %s %s", coin.ReserveBalance().String(), types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin())} + } + + commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin) + } - totalTxCost := big.NewInt(0).Add(data.InitialReserve, commission) + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), tx.GasCoin)} + } - if context.GetBalance(sender, types.GetBaseCoin()).Cmp(totalTxCost) < 0 { + if context.GetBalance(sender, types.GetBaseCoin()).Cmp(data.InitialReserve) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.InitialReserve.String(), types.GetBaseCoin())} + } + + if types.GetBaseCoin() == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.InitialReserve) + totalTxCost.Add(totalTxCost, 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 %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if context.CoinExists(data.Symbol) { @@ -108,9 +145,10 @@ func (data CreateCoinData) Run(sender types.Address, tx *Transaction, context *s } if !isCheck { - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, types.GetBaseCoin(), totalTxCost) + context.SubBalance(sender, types.GetBaseCoin(), data.InitialReserve) + context.SubBalance(sender, tx.GasCoin, commission) 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) diff --git a/core/transaction/declare_candidacy.go b/core/transaction/declare_candidacy.go index 72a5cfa4f..aea246b27 100644 --- a/core/transaction/declare_candidacy.go +++ b/core/transaction/declare_candidacy.go @@ -49,6 +49,13 @@ func (data DeclareCandidacyData) Gas() int64 { } func (data DeclareCandidacyData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response { + + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + if len(data.PubKey) != 32 { return Response{ Code: code.IncorrectPubKey, @@ -59,8 +66,8 @@ func (data DeclareCandidacyData) Run(sender types.Address, tx *Transaction, cont commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - if data.Coin != types.GetBaseCoin() { - coin := context.GetStateCoin(data.Coin) + if tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { return Response{ @@ -71,12 +78,28 @@ func (data DeclareCandidacyData) Run(sender types.Address, tx *Transaction, cont 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(data.Stake) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Stake, data.Coin)} + } - if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 { + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} + } + + if data.Coin == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.Stake) + totalTxCost.Add(totalTxCost, commission) + + if context.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if context.CandidateExists(data.PubKey) { @@ -94,9 +117,10 @@ func (data DeclareCandidacyData) Run(sender types.Address, tx *Transaction, cont // TODO: limit number of candidates to prevent flooding if !isCheck { - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, data.Coin, totalTxCost) + context.SubBalance(sender, data.Coin, data.Stake) + context.SubBalance(sender, tx.GasCoin, commission) context.CreateCandidate(data.Address, data.PubKey, data.Commission, uint(currentBlock), data.Coin, data.Stake) context.SetNonce(sender, tx.Nonce) } diff --git a/core/transaction/delegate.go b/core/transaction/delegate.go index 533db5c2c..151f95578 100644 --- a/core/transaction/delegate.go +++ b/core/transaction/delegate.go @@ -40,12 +40,19 @@ func (data DelegateData) Gas() int64 { } func (data DelegateData) Run(sender types.Address, tx *Transaction, context *state.StateDB, isCheck bool, rewardPull *big.Int, currentBlock uint64) Response { + + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + 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 tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { return Response{ @@ -56,12 +63,28 @@ func (data DelegateData) Run(sender types.Address, tx *Transaction, context *sta commission = formula.CalculateSaleAmount(coin.Volume(), coin.ReserveBalance(), coin.Data().Crr, commissionInBaseCoin) } - totalTxCost := big.NewInt(0).Add(data.Stake, commission) + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} + } - if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 { + if context.GetBalance(sender, data.Coin).Cmp(data.Stake) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Stake, data.Coin)} + } + + if data.Coin == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.Stake) + totalTxCost.Add(totalTxCost, commission) + + if context.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !context.CandidateExists(data.PubKey) { @@ -71,9 +94,10 @@ func (data DelegateData) Run(sender types.Address, tx *Transaction, context *sta } if !isCheck { - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, data.Coin, totalTxCost) + context.SubBalance(sender, tx.GasCoin, commission) + context.SubBalance(sender, data.Coin, data.Stake) context.Delegate(sender, data.PubKey, data.Coin, data.Stake) context.SetNonce(sender, tx.Nonce) } diff --git a/core/transaction/executor.go b/core/transaction/executor.go index b6b81b6d3..4fb11d9b9 100644 --- a/core/transaction/executor.go +++ b/core/transaction/executor.go @@ -10,7 +10,7 @@ import ( ) var ( - CommissionMultiplier = big.NewInt(10e15) + CommissionMultiplier = big.NewInt(10e14) ) const ( @@ -70,15 +70,11 @@ func RunTx(context *state.StateDB, isCheck bool, rawTx []byte, rewardPull *big.I Log: err.Error()} } - // 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)} - } + // TODO: deal with multiple pending transactions from one account + 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)} } return tx.decodedData.Run(sender, tx, context, isCheck, rewardPull, currentBlock) diff --git a/core/transaction/redeem_check.go b/core/transaction/redeem_check.go index 26a8df569..794d58e6c 100644 --- a/core/transaction/redeem_check.go +++ b/core/transaction/redeem_check.go @@ -58,6 +58,12 @@ func (data RedeemCheckData) Run(sender types.Address, tx *Transaction, context * Log: err.Error()} } + if tx.GasCoin != types.GetBaseCoin() { + return Response{ + Code: code.WrongGasCoin, + Log: fmt.Sprintf("Gas for for redeem check transaction can only be %s", types.GetBaseCoin())} + } + if !context.CoinExists(decodedCheck.Coin) { return Response{ Code: code.CoinNotExists, @@ -106,7 +112,7 @@ func (data RedeemCheckData) Run(sender types.Address, tx *Transaction, context * Log: err.Error()} } - if bytes.Compare(lockPublicKey, pub) != 0 { + if !bytes.Equal(lockPublicKey, pub) { return Response{ Code: code.CheckInvalidLock, Log: fmt.Sprintf("Invalid proof")} diff --git a/core/transaction/sell_all_coin.go b/core/transaction/sell_all_coin.go new file mode 100644 index 000000000..9c3b73ecc --- /dev/null +++ b/core/transaction/sell_all_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 SellAllCoinData struct { + CoinToSell types.CoinSymbol + CoinToBuy types.CoinSymbol +} + +func (data SellAllCoinData) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + CoinToSell types.CoinSymbol `json:"coin_to_sell,string"` + CoinToBuy types.CoinSymbol `json:"coin_to_buy,string"` + }{ + CoinToSell: data.CoinToSell, + CoinToBuy: data.CoinToBuy, + }) +} + +func (data SellAllCoinData) String() string { + return fmt.Sprintf("SELL ALL COIN sell:%s buy:%s", + data.CoinToBuy.String(), data.CoinToSell.String()) +} + +func (data SellAllCoinData) Gas() int64 { + return commissions.ConvertTx +} + +func (data SellAllCoinData) 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")} + } + + available := context.GetBalance(sender, data.CoinToSell) + + 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) + } + + if context.GetBalance(sender, data.CoinToSell).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)} + } + + amountToSell := big.NewInt(0).Set(available) + amountToSell.Sub(amountToSell, commission) + + if !isCheck { + rewardPull.Add(rewardPull, commissionInBaseCoin) + + context.SubBalance(sender, data.CoinToSell, available) + + 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, amountToSell) + + if !isCheck { + context.AddCoinVolume(data.CoinToBuy, value) + context.AddCoinReserve(data.CoinToBuy, amountToSell) + } + } else if data.CoinToBuy == types.GetBaseCoin() { + coin := context.GetStateCoin(data.CoinToSell).Data() + + value = formula.CalculateSaleReturn(coin.Volume, coin.ReserveBalance, coin.Crr, amountToSell) + + if !isCheck { + context.SubCoinVolume(data.CoinToSell, amountToSell) + 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, amountToSell) + value = formula.CalculatePurchaseReturn(coinTo.Volume, coinTo.ReserveBalance, coinTo.Crr, basecoinValue) + + if !isCheck { + context.AddCoinVolume(data.CoinToBuy, value) + context.SubCoinVolume(data.CoinToSell, amountToSell) + + 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/sell_coin.go b/core/transaction/sell_coin.go index b462b4ac5..75a2986b0 100644 --- a/core/transaction/sell_coin.go +++ b/core/transaction/sell_coin.go @@ -59,12 +59,18 @@ func (data SellCoinData) Run(sender types.Address, tx *Transaction, context *sta Log: fmt.Sprintf("Coin not exists")} } + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + 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 tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { return Response{ @@ -75,22 +81,33 @@ func (data SellCoinData) Run(sender types.Address, tx *Transaction, context *sta 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 { + if context.GetBalance(sender, data.CoinToSell).Cmp(data.ValueToSell) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), data.ValueToSell)} + } + + if data.CoinToSell == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.ValueToSell) + totalTxCost.Add(totalTxCost, commission) + + if context.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !isCheck { rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, data.CoinToSell, totalTxCost) + context.SubBalance(sender, data.CoinToSell, data.ValueToSell) + context.SubBalance(sender, tx.GasCoin, commission) - if data.CoinToSell != types.GetBaseCoin() { - context.SubCoinVolume(data.CoinToSell, commission) - context.SubCoinReserve(data.CoinToSell, commissionInBaseCoin) + if tx.GasCoin != types.GetBaseCoin() { + context.SubCoinVolume(tx.GasCoin, commission) + context.SubCoinReserve(tx.GasCoin, commissionInBaseCoin) } } diff --git a/core/transaction/send.go b/core/transaction/send.go index 26d786a02..30265b899 100644 --- a/core/transaction/send.go +++ b/core/transaction/send.go @@ -44,15 +44,21 @@ func (data SendData) Run(sender types.Address, tx *Transaction, context *state.S if !context.CoinExists(data.Coin) { return Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists")} + Log: fmt.Sprintf("Coin %s not exists", data.Coin)} + } + + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} } 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 tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) if coin.ReserveBalance().Cmp(commissionInBaseCoin) < 0 { return Response{ @@ -63,23 +69,40 @@ func (data SendData) Run(sender types.Address, tx *Transaction, context *state.S 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(data.Value) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Value, data.Coin)} + } - if context.GetBalance(sender, data.Coin).Cmp(totalTxCost) < 0 { + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), totalTxCost)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} + } + + if data.Coin == tx.GasCoin { + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.Value) + totalTxCost.Add(totalTxCost, commission) + + if context.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin)} + } } if !isCheck { rewardPull.Add(rewardPull, commissionInBaseCoin) - if data.Coin != types.GetBaseCoin() { - context.SubCoinVolume(data.Coin, commission) - context.SubCoinReserve(data.Coin, commissionInBaseCoin) + if tx.GasCoin != types.GetBaseCoin() { + context.SubCoinVolume(tx.GasCoin, commission) + context.SubCoinReserve(tx.GasCoin, commissionInBaseCoin) } - context.SubBalance(sender, data.Coin, totalTxCost) + context.SubBalance(sender, tx.GasCoin, commission) + context.SubBalance(sender, data.Coin, data.Value) context.AddBalance(data.To, data.Coin, data.Value) context.SetNonce(sender, tx.Nonce) } diff --git a/core/transaction/switch_candidate_status.go b/core/transaction/switch_candidate_status.go index e6b07923d..f15a65ce4 100644 --- a/core/transaction/switch_candidate_status.go +++ b/core/transaction/switch_candidate_status.go @@ -8,6 +8,7 @@ import ( "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" "math/big" ) @@ -33,13 +34,33 @@ func (data SetCandidateOnData) Gas() int64 { } 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 { + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + + commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas())) + commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) + + 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) + } + + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} } if !context.CandidateExists(data.PubKey) { @@ -50,16 +71,16 @@ func (data SetCandidateOnData) Run(sender types.Address, tx *Transaction, contex candidate := context.GetStateCandidate(data.PubKey) - if bytes.Compare(candidate.CandidateAddress.Bytes(), sender.Bytes()) != 0 { + if !bytes.Equal(candidate.CandidateAddress.Bytes(), sender.Bytes()) { return Response{ Code: code.IsNotOwnerOfCandidate, Log: fmt.Sprintf("Sender is not an owner of a candidate")} } if !isCheck { - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, types.GetBaseCoin(), commission) + context.SubBalance(sender, tx.GasCoin, commission) context.SetCandidateOnline(data.PubKey) context.SetNonce(sender, tx.Nonce) } @@ -77,7 +98,7 @@ type SetCandidateOffData struct { func (data SetCandidateOffData) MarshalJSON() ([]byte, error) { return json.Marshal(struct { - PubKey string `json:"pubkey"` + PubKey string `json:"pub_key"` }{ PubKey: fmt.Sprintf("Mp%x", data.PubKey), }) @@ -93,33 +114,53 @@ func (data SetCandidateOffData) Gas() int64 { } 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 { + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + + commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas())) + commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) + + 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) + } + + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} } if !context.CandidateExists(data.PubKey) { return Response{ Code: code.CandidateNotFound, - Log: fmt.Sprintf("Candidate with such public key not found")} + 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 { + if !bytes.Equal(candidate.CandidateAddress.Bytes(), sender.Bytes()) { return Response{ Code: code.IsNotOwnerOfCandidate, Log: fmt.Sprintf("Sender is not an owner of a candidate")} } if !isCheck { - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, types.GetBaseCoin(), commission) + context.SubBalance(sender, tx.GasCoin, commission) context.SetCandidateOffline(data.PubKey) context.SetNonce(sender, tx.Nonce) } diff --git a/core/transaction/transaction.go b/core/transaction/transaction.go index 580b8dbe2..c8e88337a 100644 --- a/core/transaction/transaction.go +++ b/core/transaction/transaction.go @@ -21,19 +21,21 @@ var ( const ( TypeSend byte = 0x01 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 + TypeSellAllCoin byte = 0x03 + TypeBuyCoin byte = 0x04 + TypeCreateCoin byte = 0x05 + TypeDeclareCandidacy byte = 0x06 + TypeDelegate byte = 0x07 + TypeUnbond byte = 0x08 + TypeRedeemCheck byte = 0x09 + TypeSetCandidateOnline byte = 0x0A + TypeSetCandidateOffline byte = 0x0B ) type Transaction struct { Nonce uint64 GasPrice *big.Int + GasCoin types.CoinSymbol Type byte Data RawData Payload []byte @@ -100,6 +102,7 @@ func (tx *Transaction) Hash() types.Hash { return rlpHash([]interface{}{ tx.Nonce, tx.GasPrice, + tx.GasCoin, tx.Type, tx.Data, tx.Payload, @@ -189,6 +192,12 @@ func DecodeFromBytes(buf []byte) (*Transaction, error) { return nil, errors.New("incorrect tx data") } } + case TypeSellAllCoin: + { + data := SellAllCoinData{} + err = rlp.Decode(bytes.NewReader(tx.Data), &data) + tx.SetDecodedData(data) + } case TypeBuyCoin: { data := BuyCoinData{} diff --git a/core/transaction/unbond.go b/core/transaction/unbond.go index 41d52a4d3..c5ded0207 100644 --- a/core/transaction/unbond.go +++ b/core/transaction/unbond.go @@ -7,6 +7,7 @@ import ( "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" ) @@ -41,13 +42,33 @@ func (data UnbondData) Gas() int64 { } 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 { + if !context.CoinExists(tx.GasCoin) { + return Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + } + + commissionInBaseCoin := big.NewInt(0).Mul(tx.GasPrice, big.NewInt(tx.Gas())) + commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoin() { + coin := context.GetStateCoin(tx.GasCoin) + + 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) + } + + if context.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %d ", sender.String(), commission)} + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin)} } if !context.CandidateExists(data.PubKey) { @@ -76,9 +97,9 @@ func (data UnbondData) Run(sender types.Address, tx *Transaction, context *state // now + 31 days unbondAtBlock := currentBlock + unbondPeriod - rewardPull.Add(rewardPull, commission) + rewardPull.Add(rewardPull, commissionInBaseCoin) - context.SubBalance(sender, types.GetBaseCoin(), commission) + context.SubBalance(sender, tx.GasCoin, 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) diff --git a/core/types/types.go b/core/types/types.go index 6e1bd21a4..a032a7b70 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -24,7 +24,6 @@ import ( "reflect" "bytes" - "github.com/MinterTeam/minter-go-node/crypto/sha3" "github.com/MinterTeam/minter-go-node/hexutil" ) @@ -194,26 +193,8 @@ func (a Address) Bytes() []byte { return a[:] } func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } func (a Address) Hash() Hash { return BytesToHash(a[:]) } -// Hex returns an EIP55-compliant hex string representation of the address. func (a Address) Hex() string { - unchecksummed := hex.EncodeToString(a[:]) - sha := sha3.NewKeccak256() - sha.Write([]byte(unchecksummed)) - hash := sha.Sum(nil) - - result := []byte(unchecksummed) - for i := 0; i < len(result); i++ { - hashByte := hash[i/2] - if i%2 == 0 { - hashByte = hashByte >> 4 - } else { - hashByte &= 0xf - } - if result[i] > '9' && hashByte > 7 { - result[i] -= 32 - } - } - return "Mx" + string(result) + return "Mx" + hex.EncodeToString(a[:]) } // String implements the stringer interface and is used also by the logger. diff --git a/crypto/crypto.go b/crypto/crypto.go index b41d468f1..62aaa6e69 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -198,9 +198,3 @@ func PubkeyToAddress(p ecdsa.PublicKey) types.Address { pubBytes := FromECDSAPub(&p) return types.BytesToAddress(Keccak256(pubBytes[1:])[12:]) } - -func zeroBytes(bytes []byte) { - for i := range bytes { - bytes[i] = 0 - } -} diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 9a3987133..88bfd54f1 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -176,7 +176,7 @@ func TestLoadECDSAFile(t *testing.T) { func TestValidateSignatureValues(t *testing.T) { check := func(expected bool, v byte, r, s *big.Int) { - if ValidateSignatureValues(v, r, s, false) != expected { + if ValidateSignatureValues(v, r, s) != expected { t.Errorf("mismatch for v: %d r: %d s: %d want: %v", v, r, s, expected) } } diff --git a/crypto/sha3/xor.go b/crypto/sha3/xor.go index 46a0d63a6..42137426b 100644 --- a/crypto/sha3/xor.go +++ b/crypto/sha3/xor.go @@ -12,5 +12,3 @@ var ( xorInUnaligned = xorInGeneric copyOutUnaligned = copyOutGeneric ) - -const xorImplementationUnaligned = "generic" diff --git a/crypto/sha3/xor_unaligned.go b/crypto/sha3/xor_unaligned.go index 929a486a7..6b7a6628c 100644 --- a/crypto/sha3/xor_unaligned.go +++ b/crypto/sha3/xor_unaligned.go @@ -54,5 +54,3 @@ var ( xorIn = xorInUnaligned copyOut = copyOutUnaligned ) - -const xorImplementationUnaligned = "unaligned" diff --git a/crypto/signature_test.go b/crypto/signature_test.go deleted file mode 100644 index aecff76bf..000000000 --- a/crypto/signature_test.go +++ /dev/null @@ -1,160 +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 crypto - -import ( - "bytes" - "crypto/ecdsa" - "reflect" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" -) - -var ( - testmsg = hexutil.MustDecode("0xce0677bb30baa8cf067c88db9811f4333d131bf8bcf12fe7065d211dce971008") - testsig = hexutil.MustDecode("0x90f27b8b488db00b00606796d2987f6a5f59ae62ea05effe84fef5b8b0e549984a691139ad57a3f0b906637673aa2f63d1f55cb1a69199d4009eea23ceaddc9301") - testpubkey = hexutil.MustDecode("0x04e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652") - testpubkeyc = hexutil.MustDecode("0x02e32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a") -) - -func TestEcrecover(t *testing.T) { - pubkey, err := Ecrecover(testmsg, testsig) - if err != nil { - t.Fatalf("recover error: %s", err) - } - if !bytes.Equal(pubkey, testpubkey) { - t.Errorf("pubkey mismatch: want: %x have: %x", testpubkey, pubkey) - } -} - -func TestVerifySignature(t *testing.T) { - sig := testsig[:len(testsig)-1] // remove recovery id - if !VerifySignature(testpubkey, testmsg, sig) { - t.Errorf("can't verify signature with uncompressed key") - } - if !VerifySignature(testpubkeyc, testmsg, sig) { - t.Errorf("can't verify signature with compressed key") - } - - if VerifySignature(nil, testmsg, sig) { - t.Errorf("signature valid with no key") - } - if VerifySignature(testpubkey, nil, sig) { - t.Errorf("signature valid with no message") - } - if VerifySignature(testpubkey, testmsg, nil) { - t.Errorf("nil signature valid") - } - if VerifySignature(testpubkey, testmsg, append(common.CopyBytes(sig), 1, 2, 3)) { - t.Errorf("signature valid with extra bytes at the end") - } - if VerifySignature(testpubkey, testmsg, sig[:len(sig)-2]) { - t.Errorf("signature valid even though it's incomplete") - } - wrongkey := common.CopyBytes(testpubkey) - wrongkey[10]++ - if VerifySignature(wrongkey, testmsg, sig) { - t.Errorf("signature valid with with wrong public key") - } -} - -// This test checks that VerifySignature rejects malleable signatures with s > N/2. -func TestVerifySignatureMalleable(t *testing.T) { - sig := hexutil.MustDecode("0x638a54215d80a6713c8d523a6adc4e6e73652d859103a36b700851cb0e61b66b8ebfc1a610c57d732ec6e0a8f06a9a7a28df5051ece514702ff9cdff0b11f454") - key := hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138") - msg := hexutil.MustDecode("0xd301ce462d3e639518f482c7f03821fec1e602018630ce621e1e7851c12343a6") - if VerifySignature(key, msg, sig) { - t.Error("VerifySignature returned true for malleable signature") - } -} - -func TestDecompressPubkey(t *testing.T) { - key, err := DecompressPubkey(testpubkeyc) - if err != nil { - t.Fatal(err) - } - if uncompressed := FromECDSAPub(key); !bytes.Equal(uncompressed, testpubkey) { - t.Errorf("wrong public key result: got %x, want %x", uncompressed, testpubkey) - } - if _, err := DecompressPubkey(nil); err == nil { - t.Errorf("no error for nil pubkey") - } - if _, err := DecompressPubkey(testpubkeyc[:5]); err == nil { - t.Errorf("no error for incomplete pubkey") - } - if _, err := DecompressPubkey(append(common.CopyBytes(testpubkeyc), 1, 2, 3)); err == nil { - t.Errorf("no error for pubkey with extra bytes at the end") - } -} - -func TestCompressPubkey(t *testing.T) { - key := &ecdsa.PublicKey{ - Curve: S256(), - X: math.MustParseBig256("0xe32df42865e97135acfb65f3bae71bdc86f4d49150ad6a440b6f15878109880a"), - Y: math.MustParseBig256("0x0a2b2667f7e725ceea70c673093bf67663e0312623c8e091b13cf2c0f11ef652"), - } - compressed := CompressPubkey(key) - if !bytes.Equal(compressed, testpubkeyc) { - t.Errorf("wrong public key result: got %x, want %x", compressed, testpubkeyc) - } -} - -func TestPubkeyRandom(t *testing.T) { - const runs = 200 - - for i := 0; i < runs; i++ { - key, err := GenerateKey() - if err != nil { - t.Fatalf("iteration %d: %v", i, err) - } - pubkey2, err := DecompressPubkey(CompressPubkey(&key.PublicKey)) - if err != nil { - t.Fatalf("iteration %d: %v", i, err) - } - if !reflect.DeepEqual(key.PublicKey, *pubkey2) { - t.Fatalf("iteration %d: keys not equal", i) - } - } -} - -func BenchmarkEcrecoverSignature(b *testing.B) { - for i := 0; i < b.N; i++ { - if _, err := Ecrecover(testmsg, testsig); err != nil { - b.Fatal("ecrecover error", err) - } - } -} - -func BenchmarkVerifySignature(b *testing.B) { - sig := testsig[:len(testsig)-1] // remove recovery id - for i := 0; i < b.N; i++ { - if !VerifySignature(testpubkey, testmsg, sig) { - b.Fatal("verify error") - } - } -} - -func BenchmarkDecompressPubkey(b *testing.B) { - for i := 0; i < b.N; i++ { - if _, err := DecompressPubkey(testpubkeyc); err != nil { - b.Fatal(err) - } - } -} diff --git a/docker-compose.yml b/docker-compose.yml index 179da8cda..fde5b81eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,7 @@ version: "3.4" services: minter: - image: minterteam/minter:0.0.6 - command: --tendermint_addr=tcp://tendermint:46657 + image: minterteam/minter:0.1.0 volumes: - ~/.minter:/minter ports: @@ -13,23 +12,4 @@ services: interval: 5s timeout: 5s retries: 3 - start_period: 30s - tendermint: - image: tendermint/tendermint:0.22.4 - command: node --proxy_app=tcp://minter:46658 - volumes: - - ~/.tendermint:/tendermint - ports: - - "46656:46656" - - "46657:46657" - restart: always - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:46657/health"] - interval: 5s - timeout: 5s - retries: 3 - start_period: 30s - ulimits: - nofile: - soft: 1048576 - hard: 1048576 + start_period: 30s \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst index 7edf453c6..aa74bdf61 100755 --- a/docs/api.rst +++ b/docs/api.rst @@ -20,13 +20,47 @@ normal mode. .. code-block:: json { - "code": 0, - "result": { - "latest_block_hash": "30AAD93FC07CBFC7ABC9E34D6FDC29FF0928A5C5", - "latest_app_hash": "8D10D20C2BC74AAF82ABC41ADA9852D5EF89DDE17382CED2C21B84BE36365583", - "latest_block_height": 29783, - "latest_block_time": "2018-06-21T13:58:53.078510484+03:00" - } + "code":0, + "result":{ + "version":"0.1.0", + "latest_block_hash":"BF2647887AEBF12ABF92D240613907E84E757E34", + "latest_app_hash":"C92D2073E15519C0D684A896AF8DF9AAD536423A9564987F979CFCC13FBE57D7", + "latest_block_height":81, + "latest_block_time":"2018-07-20T16:03:42.001313931+03:00", + "tm_status":{ + "node_info":{ + "id":"30231c71e87db942ea902ad6ad22cfefa3b15560", + "listen_addr":"192.168.1.102:26656", + "network":"minter-test-network-11-private", + "version":"0.22.4", + "channels":"4020212223303800", + "moniker":"MinterNode", + "other":[ + "amino_version=0.10.1", + "p2p_version=0.5.0", + "consensus_version=v1/0.2.2", + "rpc_version=0.7.0/3", + "tx_index=on", + "rpc_addr=tcp://0.0.0.0:26657" + ] + }, + "sync_info":{ + "latest_block_hash":"BF2647887AEBF12ABF92D240613907E84E757E34", + "latest_app_hash":"C92D2073E15519C0D684A896AF8DF9AAD536423A9564987F979CFCC13FBE57D7", + "latest_block_height":"81", + "latest_block_time":"2018-07-20T13:03:42.001313931Z", + "catching_up":false + }, + "validator_info":{ + "address":"F974AA1C211BC294DAB21B4F5866603144E025E8", + "pub_key":{ + "type":"tendermint/PubKeyEd25519", + "value":"YfdhnC3qkBZqgQl76+lY99f0xfGJLyTdgTOLJ2CSvnA=" + }, + "voting_power":"0" + } + } + } } Volume of Base Coin in Blockchain @@ -42,8 +76,10 @@ relayed rewards. .. code-block:: json { - "code": 0, - "result": "20000111000000000000000000" + "code":0, + "result":{ + "volume":"20000222000000000000000000" + } } Candidate @@ -74,26 +110,26 @@ found. .. code-block:: json { - "code": 0, - "result": { - "candidate": { - "candidate_address": "Mx655a96de0e7928bf78c41f555010391581a5afab", - "total_stake": "49500000000000000000", - "pub_key": "Mp34e647f46a5dd89e9f21acdbd0c45c8c768fdc17082d0783b683bfb0da7ce989", - "commission": 50, - "accumulated_reward": "0", - "stakes": [ - { - "owner": "Mx655a96de0e7928bf78c41f555010391581a5afab", - "coin": "MNT", - "value": "49500000000000000000" - } - ], - "created_at_block": 27447, - "status": 1, - "absent_times": 0 - } - } + "code":0, + "result":{ + "candidate":{ + "candidate_address":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "total_stake":"1", + "pub_key":"Mpc0d436ce0a9e7129cb3dbbfb059ec3a45865305a4102bc68cf6ed41d41d53e99", + "commission":10, + "accumulated_reward":"0", + "stakes":[ + { + "owner":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "coin":"MNT", + "value":"1" + } + ], + "created_at_block":1, + "status":2, + "absent_times":0 + } + } } Validators @@ -108,26 +144,26 @@ Returns list of active validators. .. code-block:: json { - "code": 0, - "result": [ - { - "candidate_address": "Mx655a96de0e7928bf78c41f555010391581a5afab", - "total_stake": "49500000000000000000", - "pub_key": "Mp34e647f46a5dd89e9f21acdbd0c45c8c768fdc17082d0783b683bfb0da7ce989", - "commission": 50, - "accumulated_reward": "0", - "stakes": [ - { - "owner": "Mx655a96de0e7928bf78c41f555010391581a5afab", - "coin": "MNT", - "value": "49500000000000000000" - } - ], - "created_at_block": 27447, - "status": 1, - "absent_times": 0 - } - ] + "code":0, + "result":[ + { + "candidate_address":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "total_stake":"1", + "pub_key":"Mpc0d436ce0a9e7129cb3dbbfb059ec3a45865305a4102bc68cf6ed41d41d53e99", + "commission":10, + "accumulated_reward":"666000000000000000000", + "stakes":[ + { + "owner":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "coin":"MNT", + "value":"1" + } + ], + "created_at_block":1, + "status":2, + "absent_times":0 + } + ] } Balance @@ -142,10 +178,12 @@ Returns balance of an account. .. code-block:: json { - "code": 0, - "result": { - "MNT": "670983232356790123336" - } + "code":0, + "result":{ + "balance":{ + "MNT":"100011877000000000000000000" + } + } } **Result**: Map of balances. CoinSymbol => Balance (in pips). @@ -163,8 +201,10 @@ transaction. .. code-block:: json { - "code": 0, - "result": 3 + "code":0, + "result":{ + "count":1 + } } **Result**: Count of transactions sent from given account. @@ -182,7 +222,9 @@ Sends transaction to the Minter Network. { "code": 0, - "result": "Mtfd5c3ecad1e8333564cf6e3f968578b9db5acea3" + "result": { + "hash": "Mtfd5c3ecad1e8333564cf6e3f968578b9db5acea3" + } } **Result**: Transaction hash. @@ -190,8 +232,6 @@ Sends transaction to the Minter Network. Transaction ^^^^^^^^^^^ -*In development* - .. code-block:: bash curl -s 'localhost:8841/api/transaction/{hash}' @@ -199,8 +239,48 @@ Transaction .. code-block:: json { - "code": 0, - "result": {} + "code":0, + "result":{ + "hash":"47B0CF9BFAA60CA343392FBE1E366EB221231F38", + "raw_tx":"f873010101aae98a4d4e540000000000000094a93163fdf10724dc4785ff5cbfb9ac0b5949409f880de0b6b3a764000080801ba0da1b6fd187bc5c757d1d1497d03471a3b5d1fd4d8025859ea127841975ce0df4a0158b54aaf8066be9ef26aae9f1a953777c346e58a6c6f45eb2d465efea74e5af", + "height":41, + "index":0, + "tx_result":{ + "gas_wanted":10, + "gas_used":10, + "tags":[ + { + "key":"dHgudHlwZQ==", + "value":"AQ==" + }, + { + "key":"dHguZnJvbQ==", + "value":"YTkzMTYzZmRmMTA3MjRkYzQ3ODVmZjVjYmZiOWFjMGI1OTQ5NDA5Zg==" + }, + { + "key":"dHgudG8=", + "value":"YTkzMTYzZmRmMTA3MjRkYzQ3ODVmZjVjYmZiOWFjMGI1OTQ5NDA5Zg==" + }, + { + "key":"dHguY29pbg==", + "value":"TU5U" + } + ], + "fee":{ + + } + }, + "from":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "nonce":1, + "gas_price":1, + "type":1, + "data":{ + "coin":"MNT", + "to":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "value":"1000000000000000000" + }, + "payload":"" + } } Block @@ -217,44 +297,73 @@ Returns block data at given height. { "code":0, "result":{ - "hash":"A83F3A3909C8B863305C5A444C8C34C514A03590", - "height":108805, - "time":"2018-07-03T09:46:54.359423195Z", + "hash":"8E07E206FBB41D7697D105CBC7FE477DDFAA2D5B", + "height":41, + "time":"2018-07-20T13:00:21.575014435Z", "num_txs":1, - "total_txs":1174135, + "total_txs":1, "transactions":[ { - "hash":"Mt3f85c77911f058c9c2f79d73c5d68b2c7dd3c2cd", - "from":"Mxa93163fdF10724DC4785FF5cBfB9aC0B5949409F", - "nonce":81, - "gasPrice":1, - "type":5, + "hash":"Mt47b0cf9bfaa60ca343392fbe1e366eb221231f38", + "raw_tx":"f873010101aae98a4d4e540000000000000094a93163fdf10724dc4785ff5cbfb9ac0b5949409f880de0b6b3a764000080801ba0da1b6fd187bc5c757d1d1497d03471a3b5d1fd4d8025859ea127841975ce0df4a0158b54aaf8066be9ef26aae9f1a953777c346e58a6c6f45eb2d465efea74e5af", + "from":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "nonce":1, + "gas_price":1, + "type":1, "data":{ - "PubKey":"Mp079138d379aaf423c911506a3ccbe1d590a7d4d9aecbc7eb05816d81b41848d6", - "Coin":"BLTCOIN", - "Stake":"2000000000000000000" + "coin":"MNT", + "to":"Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", + "value":"1000000000000000000" }, "payload":"", - "serviceData":"", - "gas":10000 + "service_data":"", + "gas":10, + "tx_result":{ + "gas_wanted":10, + "gas_used":10, + "tags":[ + { + "key":"dHgudHlwZQ==", + "value":"AQ==" + }, + { + "key":"dHguZnJvbQ==", + "value":"YTkzMTYzZmRmMTA3MjRkYzQ3ODVmZjVjYmZiOWFjMGI1OTQ5NDA5Zg==" + }, + { + "key":"dHgudG8=", + "value":"YTkzMTYzZmRmMTA3MjRkYzQ3ODVmZjVjYmZiOWFjMGI1OTQ5NDA5Zg==" + }, + { + "key":"dHguY29pbg==", + "value":"TU5U" + } + ], + "fee":{ + + } + } } ], "precommits":[ { - "validator_address":"04E5DCA0DFCF35605A3EB1292DBDBF7C97B476B8", - "validator_index":0, - "height":108804, - "round":0, - "timestamp":"2018-07-03T09:47:33.79209988Z", + "validator_address":"8055BB821C535279E169FDF60BBEBEBE1452DBA8", + "validator_index":"0", + "height":"40", + "round":"0", + "timestamp":"2018-07-20T13:00:16.571443571Z", "type":2, "block_id":{ - "hash":"2222959DA3EEA441DB6D0E01C12F1546B210DA72", + "hash":"0F06CA442183BED91E66010314FA6CADBC598801", "parts":{ - "total":1, - "hash":"3821D8B2A09A1C6932712523B8DEB588375D7BFA" + "total":"1", + "hash":"3D119516E329A211B74D728728A7E283E3BC956E" } }, - "signature":[] + "signature":{ + "type":"tendermint/SignatureEd25519", + "value":"lhNyaFgSYmC7YF/FPSwZ2yksWwViaclK6rGwdN2+nVnp/uMQherRMyZv6hJB/YedAjgo49/fBhGZUcyOO7Y+AA==" + } } ] } @@ -311,7 +420,10 @@ Request params: { "code": 0, - "result": "29808848728151191" + "result": { + "will_get": "29808848728151191", + "commission": "443372813245" + } } **Result**: Amount of "to_coin" user should get. @@ -335,7 +447,10 @@ Request params: { "code": 0, - "result": "29808848728151191" + "result": { + "will_pay": "29808848728151191", + "commission": "443372813245" + } } **Result**: Amount of "to_coin" user should give. diff --git a/docs/coins.rst b/docs/coins.rst index c54a3ebfd..0ff6a71aa 100755 --- a/docs/coins.rst +++ b/docs/coins.rst @@ -6,9 +6,16 @@ Minter Blockchain is multi-coin system. | Base coin in testnet is ``MNT``. | Base coin in mainnet is ``BIP``. -| Smallest part of a coin is called ``pip``. +| Smallest part of *each* coin is called ``pip``. | 1 pip = 1^-18 of any coin. In Blockchain and API we only operating with pips. +**Note:** +Each coin has its **own** pip. You should treat pip like atomic part of a coin, not as currency. + +| 1 MNT = 10^18 pip (MNT's pip) +| 1 ABC = 10^18 pip (ABC's pip) +| 1 MNT != 1 ABC + Coin Issuance ^^^^^^^^^^^^^ @@ -18,7 +25,7 @@ Issue own coin is as simple as filling a form with given fields: .. figure:: assets/coin-minter.png :width: 300px -- **Coin name** - Name of a coin. Arbitrary string. +- **Coin name** - Name of a coin. Arbitrary string up to 64 letters length. - **Coin symbol** - Symbol of a coin. Must be unique, alphabetic, uppercase, 3 to 10 letters length. - **Initial supply** - Amount of coins to issue. Issued coins will be available to sender account. - **Initial reserve** - Initial reserve in base coin. diff --git a/docs/commissions.rst b/docs/commissions.rst index 9556bdd6c..4fa407328 100755 --- a/docs/commissions.rst +++ b/docs/commissions.rst @@ -13,25 +13,27 @@ Here is a list of current fees: +----------------------------------+---------------------+ | Type | Fee | +==================================+=====================+ -| **TypeSend** | 10 units | +| **TypeSend** | 10 units | +----------------------------------+---------------------+ -| **TypeSellCoin** | 100 units | +| **TypeSellCoin** | 100 units | +----------------------------------+---------------------+ -| **TypeBuyCoin** | 100 units | +| **TypeSellAllCoin** | 100 units | +----------------------------------+---------------------+ -| **TypeCreateCoin** | 1000 units | +| **TypeBuyCoin** | 100 units | +----------------------------------+---------------------+ -| **TypeDeclareCandidacy** | 10000 units | +| **TypeCreateCoin** | 1000 units | +----------------------------------+---------------------+ -| **TypeDelegate** | 100 units | +| **TypeDeclareCandidacy** | 10000 units | +----------------------------------+---------------------+ -| **TypeUnbond** | 100 units | +| **TypeDelegate** | 100 units | +----------------------------------+---------------------+ -| **TypeRedeemCheck** | 10 units | +| **TypeUnbond** | 100 units | +----------------------------------+---------------------+ -| **TypeSetCandidateOnline** | 100 units | +| **TypeRedeemCheck** | 10 units | +----------------------------------+---------------------+ -| **TypeSetCandidateOffline** | 100 units | +| **TypeSetCandidateOnline** | 100 units | ++----------------------------------+---------------------+ +| **TypeSetCandidateOffline** | 100 units | +----------------------------------+---------------------+ Also sender should pay extra 2 units per byte in Payload and Service Data fields. diff --git a/docs/delegator-faq.rst b/docs/delegator-faq.rst index ddeec27e1..9d863fa4a 100755 --- a/docs/delegator-faq.rst +++ b/docs/delegator-faq.rst @@ -4,17 +4,91 @@ Delegator FAQ What is a delegator? ^^^^^^^^^^^^^^^^^^^^ -Choosing a validator -^^^^^^^^^^^^^^^^^^^^ +People that cannot, or do not want to run validator operations, can still participate in +the staking process as delegators. Indeed, validators are not chosen based on their own +stake but based on their total stake, which is the sum of their own stake and of the stake +that is delegated to them. This is an important property, as it makes delegators a +safeguard against validators that exhibit bad behavior. If a validator misbehaves, its +delegators will move their staked coins away from it, thereby reducing its stake. Eventually, +if a validator's stake falls under the top addresses with highest stake, it will exit the +validator set. + +Delegators share the revenue of their validators, but they also share the risks. In terms +of revenue, validators and delegators differ in that validators can apply a commission on +the revenue that goes to their delegator before it is distributed. This commission is +known to delegators beforehand and cannot be changed. In terms of risk, delegators' coins +can be slashed if their validator misbehaves. For more, see Risks section. + +To become delegators, coin holders need to send a "Delegate transaction" where they specify +how many coins they want to bond and to which validator. Later, if a delegator wants to +unbond part or all of its stake, it needs to send an "Unbond transaction". From there, the +delegator will have to wait 30 days to retrieve its coins. Directives of delegators ^^^^^^^^^^^^^^^^^^^^^^^^ +Being a delegator is not a passive task. Here are the main directives of a delegator: + +- Perform careful due diligence on validators before delegating. If a validator misbehaves, + part of its total stake, which includes the stake of its delegators, can be slashed. Delegators + should therefore carefully select validators they think will behave correctly. + +- Actively monitor their validator after having delegated. Delegators should ensure that the + validators they're delegating to behaves correctly, meaning that they have good uptime, do not + get hacked and participate in governance. If a delegator is not satisfied with its validator, + it can unbond and switch to another validator. + Revenue ^^^^^^^ +Validators and delegators earn revenue in exchange for their services. This revenue is given in three forms: + +- Block rewards +- Transaction fees: Each transaction on the Minter Network comes with transactions fees. Fees are distributed to + validators and delegators in proportion to their stake. + Validator's commission ^^^^^^^^^^^^^^^^^^^^^^ +Each validator's staking pool receives revenue in proportion to its total stake. However, before this revenue is +distributed to delegators inside the staking pool, the validator can apply a commission. In other words, delegators +have to pay a commission to their validators on the revenue they earn. + +``5%`` from reward going to DAO account. + +Lets consider a validator whose stake (i.e. self-bonded stake + delegated stake) is 10% of the total stake of all +validators. This validator has 20% self-bonded stake and applies a commission of 10%. Now let us consider a block +with the following revenue: + +- 111 Bips as block reward +- 10 Bips as transaction fees. + +This amounts to a total of 121 Bips to be distributed among all staking pools. + +Our validator's staking pool represents 10% of the total stake, which means the pool obtains 12.1 bips. Now let us +look at the internal distribution of revenue: + +- Commission = 10% * 80% * 12.1 bips = 0.69696 bips +- Validator's revenue = 20% * 12.1 bips + Commission = 3.11696 bips +- Delegators' total revenue = 80% * 12.1 bips - Commission = 8.98304 bips + +Then, each delegator in the staking pool can claim its portion of the delegators' total revenue. + Risks ^^^^^ + +Staking coins is not free of risk. First, staked coins are locked up, and retrieving them requires a 30 days waiting +period called unbonding period. Additionally, if a validator misbehaves, a portion of its total stake can be slashed +(i.e. destroyed). This includes the stake of their delegators. + +There are 2 main slashing conditions: + +- **Double signing**: If someone reports on chain A that a validator signed two blocks at the same height on chain + A and chain B, this validator will get slashed on chain A +- **Unavailability**: If a validator's signature has not been included in the last 12 blocks, + 1% of stake will get slashed and validator will be turned off + +This is why delegators should perform careful due diligence on validators before delegating. It is also important +that delegators actively monitor the activity of their validators. If a validator behaves suspiciously or is too +often offline, delegators can choose to unbond from it or switch to another validator. Delegators can also mitigate +risk by distributing their stake across multiple validators. diff --git a/docs/install.rst b/docs/install.rst index cecb2bc06..99836a64d 100755 --- a/docs/install.rst +++ b/docs/install.rst @@ -5,6 +5,25 @@ Install Minter There are several ways you can install Minter Blockchain Testnet node on your machine: +Using binary +------------ + +Download Minter +^^^^^^^^^^^^^^^ + +Get `latest binary build `__ suitable for your architecture and +unpack it to desired folder. + +Run Minter +^^^^^^^^^^ + +.. code-block:: bash + :lineno-start: 13 + + ./minter + +Then open http://localhost:3000/ in local browser to see node's GUI. + Using Docker ------------ @@ -19,21 +38,6 @@ Clone Minter source code to your machine git clone https://github.com/MinterTeam/minter-go-node.git cd minter-go-node - -Prepare folders and configs -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: bash - :lineno-start: 3 - - mkdir -p ~/.tendermint/data - mkdir -p ~/.minter/data - - cp -R networks/testnet/ ~/.tendermint/config - - chmod -R 0777 ~/.tendermint - chmod -R 0777 ~/.minter - Start Minter ^^^^^^^^^^^^ @@ -42,6 +46,7 @@ Start Minter docker-compose up +Then open http://localhost:3000/ in local browser to see node's GUI. From Source ----------- @@ -49,10 +54,6 @@ From Source You'll need ``go`` `installed `__ and the required `environment variables set `__ -Install Tendermint 0.22.4 -^^^^^^^^^^^^^^^^^^^^^^^^^ -`Read official instructions `__ - Clone Minter source code to your machine ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -92,31 +93,6 @@ to put the binary in ``./build``. The latest ``minter version`` is now installed. -Create data directories -^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: bash - :lineno-start: 9 - - mkdir -p ~/.tendermint/data - mkdir -p ~/.minter/data - -Copy config and genesis file -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: bash - :lineno-start: 11 - - cp -R networks/testnet/ ~/.tendermint/config - -Run Tendermint -^^^^^^^^^^^^^^ - -.. code-block:: bash - :lineno-start: 12 - - tendermint node - Run Minter ^^^^^^^^^^ @@ -125,6 +101,8 @@ Run Minter minter +Then open http://localhost:3000/ in local browser to see node's GUI. + Troubleshooting --------------- diff --git a/docs/running-in-production.rst b/docs/running-in-production.rst index f0d407f10..b60ba838d 100644 --- a/docs/running-in-production.rst +++ b/docs/running-in-production.rst @@ -6,7 +6,7 @@ DOS Exposure and Mitigation Validators are supposed to setup `Sentry Node Architecture `__ -to prevent Denial-of-service attacks. You can read more about it `here +to prevent Denial-of-service attacks. `Read more about it `__. P2P @@ -40,9 +40,9 @@ wrong. Other useful endpoints include mentioned earlier `/status`, `/net_info` and `/validators`. -We have a small tool, called tm-monitor, which outputs information from the -endpoints above plus some statistics. The tool can be found `here -`__. +We have a small tool, called `tm-monitor +`__, which outputs information from the +endpoints above plus some statistics. Monitoring Minter @@ -90,8 +90,8 @@ Operating Systems ~~~~~~~~~~~~~~~~~ Tendermint and Minter can be compiled for a wide range of operating systems thanks to Go -language (the list of $OS/$ARCH pairs can be found `here -`__). +language. `List of $OS/$ARCH pairs +`__. While we do not favor any operation system, more secure and stable Linux server distributions (like Centos) should be preferred over desktop operation systems diff --git a/docs/transactions.rst b/docs/transactions.rst index 9fca7b0c8..51a1d8638 100755 --- a/docs/transactions.rst +++ b/docs/transactions.rst @@ -51,21 +51,23 @@ Type of transaction is determined by a single byte. +----------------------------------+---------+ | **TypeSellCoin** | 0x02 | +----------------------------------+---------+ -| **TypeBuyCoin** | 0x03 | +| **TypeSellAllCoin** | 0x03 | +----------------------------------+---------+ -| **TypeCreateCoin** | 0x04 | +| **TypeBuyCoin** | 0x04 | +----------------------------------+---------+ -| **TypeDeclareCandidacy** | 0x05 | +| **TypeCreateCoin** | 0x05 | +----------------------------------+---------+ -| **TypeDelegate** | 0x06 | +| **TypeDeclareCandidacy** | 0x06 | +----------------------------------+---------+ -| **TypeUnbond** | 0x07 | +| **TypeDelegate** | 0x07 | +----------------------------------+---------+ -| **TypeRedeemCheck** | 0x08 | +| **TypeUnbond** | 0x08 | +----------------------------------+---------+ -| **TypeSetCandidateOnline** | 0x09 | +| **TypeRedeemCheck** | 0x09 | +----------------------------------+---------+ -| **TypeSetCandidateOffline** | 0x0A | +| **TypeSetCandidateOnline** | 0x0A | ++----------------------------------+---------+ +| **TypeSetCandidateOffline** | 0x0B | +----------------------------------+---------+ Send transaction @@ -110,10 +112,29 @@ Transaction for selling one coin (owned by sender) in favour of another coin in | **ValueToSell** - Amount of **CoinToSell** to give. | **CoinToBuy** - Symbol of a coin to get. +Sell all coin transaction +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Type: **0x03** + +Transaction for selling all existing coins of one type (owned by sender) in favour of another coin in a system. + +*Data field contents:* + +.. code-block:: go + + type SellAllCoinData struct { + CoinToSell [10]byte + CoinToBuy [10]byte + } + +| **CoinToSell** - Symbol of a coin to give. +| **CoinToBuy** - Symbol of a coin to get. + Buy coin transaction ^^^^^^^^^^^^^^^^^^^^ -Type: **0x03** +Type: **0x04** Transaction for buy a coin paying another coin (owned by sender). @@ -134,7 +155,7 @@ Transaction for buy a coin paying another coin (owned by sender). Create coin transaction ^^^^^^^^^^^^^^^^^^^^^^^ -Type: **0x04** +Type: **0x05** Transaction for creating new coin in a system. @@ -150,7 +171,7 @@ Transaction for creating new coin in a system. ConstantReserveRatio uint } -| **Name** - Name of a coin. Arbitrary string. +| **Name** - Name of a coin. Arbitrary string up to 64 letters length. | **Symbol** - Symbol of a coin. Must be unique, alphabetic, uppercase, 3 to 10 symbols length. | **InitialAmount** - Amount of coins to issue. Issued coins will be available to sender account. | **InitialReserve** - Initial reserve in BIP's. @@ -159,7 +180,7 @@ Transaction for creating new coin in a system. Declare candidacy transaction ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Type: **0x05** +Type: **0x06** Transaction for declaring new validator candidacy. @@ -184,7 +205,7 @@ Transaction for declaring new validator candidacy. Delegate transaction ^^^^^^^^^^^^^^^^^^^^ -Type: **0x06** +Type: **0x07** Transaction for delegating funds to validator. @@ -203,9 +224,9 @@ Transaction for delegating funds to validator. | **Stake** - Amount of coins to stake. Unbond transaction -^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^ -Type: **0x07** +Type: **0x08** Transaction for unbonding funds from validator's stake. @@ -226,7 +247,7 @@ Transaction for unbonding funds from validator's stake. Redeem check transaction ^^^^^^^^^^^^^^^^^^^^^^^^ -Type: **0x08** +Type: **0x09** Transaction for redeeming a check. @@ -245,7 +266,7 @@ Transaction for redeeming a check. Set candidate online transaction ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Type: **0x09** +Type: **0x0A** Transaction for turning candidate on. This transaction should be sent from address which is set in the "Declare candidacy transaction". @@ -262,7 +283,7 @@ Transaction for turning candidate on. This transaction should be sent from addre Set candidate offline transaction ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Type: **0x0A** +Type: **0x0B** Transaction for turning candidate off. This transaction should be sent from address which is set in the "Declare candidacy transaction". diff --git a/docs/validators.rst b/docs/validators.rst index 7c2b4b063..c000b1ced 100755 --- a/docs/validators.rst +++ b/docs/validators.rst @@ -23,9 +23,9 @@ Requirements Minimal requirements for running Validator's Node are: -- 2GB RAM -- 100GB of disk space -- 1.4 GHz 2v CPU +- 4GB RAM +- 200GB SSD +- x64 2.0 GHz 4 vCPUs SSD disks are preferable for high transaction throughput. @@ -33,7 +33,8 @@ Recommended: - 4GB RAM - 200GB SSD -- x64 2.0 GHz 4v CPU +- x64 3.4 GHz 8 vCPUs +- HSM Validators limitations ^^^^^^^^^^^^^^^^^^^^^^ @@ -49,6 +50,8 @@ Rewards Rewards for blocks and commissions are accumulated and proportionally (based on stake value) payed once per ``12 blocks`` (approx 1 minute) to all active validators (and their delegators). +Block rewards are configured to decrease from 111 to 0 BIP (MNT) in 7 years. + Delegators receive their rewards at the same time after paying commission to their validators (commission value is based on validator's settings). @@ -69,7 +72,7 @@ of funds for a validator and its delegators: - **Double signing**: If someone reports on chain A that a validator signed two blocks at the same height on chain A and chain B, this validator will get slashed on chain A -- **Unavailability**: If a validator's signature has not been included in the last X blocks, +- **Unavailability**: If a validator's signature has not been included in the last 12 blocks, 1% of stake will get slashed and validator will be turned off Note that even if a validator does not intentionally misbehave, it can still be slashed if its node crashes, @@ -81,51 +84,101 @@ Becoming validator in testnet 1. Install and run Minter Full Node. See :ref:`install-minter`. Make sure your node successfully synchronized. -2. Generate and install validator's key using our `tool `__. - If you already have ``priv_validator.json`` file – just replace it with new one. - -3. Restart Minter Node and Tendermint. - Restarting will apply changes to ``priv_validator.json`` file. +2. Get your validator's public key from `Minter GUI `__. -4. Go to `Vault `__ and send 2 transactions: +3. Go to `Vault `__ and send 2 transactions: Fill and send ``Declare candidacy`` and ``Set candidate online`` forms. - If you cannot open Vault because of invalid certificate: - `reset HSTS `__ for domains - ``minter.network`` and ``vault.minter.network``. Then try to open - `HTTP version of Vault `__. - P.S. You can receive testnet coins in our telegram wallet @BipWallet_Bot. - 4.1. Declare candidacy + 3.1. Declare candidacy Validators should declare their candidacy, after which users can delegate and, if they so wish, unbond. Then declaring candidacy validator should fill a form: - Address - You will receive rewards to this address and will be able to on/off your validator. - - Public Key - Paste public key you created in step 2 *(Mp...)*. + - Public Key - Paste public key from step 2 *(Mp...)*. - Commission - Set commission for delegated stakes. - - Coin - Enter coin of your stake (MNT). + - Coin - Enter coin of your stake (i.e. MNT). - Stake - Enter value of your stake in given coin. .. figure:: assets/vault-declare.png :width: 300px - 4.2. Set candidate online + 3.2. Set candidate online Validator is **offline** by default. When offline, validator is not included in the list of Minter Blockchain validators, so he is not receiving any rewards and cannot be punished for low availability. - To turn your validator **on**, you should provide Public Key (which you created in step - 2 *(Mp...)*). + To turn your validator **on**, you should provide Public Key (from step 2 *(Mp...)*). - *Note: You should send transaction from address you choose in Address field in step 4.2* + *Note: You should send transaction from address you choose in Address field in step 3.2* .. figure:: assets/vault-candidate-on.png :width: 300px -5. Done. +4. Done. Now you will receive reward as long as your node is running and available. DDOS protection. Sentry node architecture ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Denial-of-service attacks occur when an attacker sends a flood of internet traffic to an IP +address to prevent the server at the IP address from connecting to the internet. + +An attacker scans the network, tries to learn the IP address of various validator +nodes and disconnect them from communication by flooding them with traffic. + +One recommended way to mitigate these risks is for validators to carefully +structure their network topology in a so-called sentry node architecture. + +Validator nodes should only connect to full-nodes they trust because they +operate them themselves or are run by other validators they know socially. +A validator node will typically run in a data center. Most data centers provide +direct links the networks of major cloud providers. The validator can use +those links to connect to sentry nodes in the cloud. This shifts the burden +of denial-of-service from the validator's node directly to its sentry nodes, +and may require new sentry nodes be spun up or activated to mitigate attacks +on existing ones. + +Sentry nodes can be quickly spun up or change their IP addresses. Because +the links to the sentry nodes are in private IP space, an internet based +attacked cannot disturb them directly. This will ensure validator block +proposals and votes always make it to the rest of the network. + +It is expected that good operating procedures on that part of validators will +completely mitigate these threats. + +Practical instructions +---------------------- + +To setup your sentry node architecture you can follow the instructions below: + +Validators nodes should edit their ``config.toml``: + +:: + + # Comma separated list of nodes to keep persistent connections to + # Do not add private peers to this list if you don't want them advertised + persistent_peers = [list of sentry nodes] + + # Set true to enable the peer-exchange reactor + pex = false + + # Disable transaction indexer for better performance + indexer = "null" + index_all_tags = false + +Sentry Nodes should edit their ``config.toml``: + +:: + + # Comma separated list of peer IDs to keep private (will not be gossiped to other peers) + private_peer_ids = "ipaddress of validator nodes" + + +Also you can disable Minter API on Validator node to improve performance: + +:: + + minter --disable-api diff --git a/genesis/genesis.go b/genesis/genesis.go index 2120a8ce3..5e7ffa592 100644 --- a/genesis/genesis.go +++ b/genesis/genesis.go @@ -1,6 +1,64 @@ package genesis -import "github.com/MinterTeam/minter-go-node/core/types" +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + tmtypes "github.com/tendermint/tendermint/types" + "math/big" + "time" +) + +func GetTestnetGenesis() *tmtypes.GenesisDoc { + + validatorPubKeyBytes, _ := base64.StdEncoding.DecodeString("qu4d3zD/VMkHFdkotWZS/FEb7Tci5Ylz6O+Ub12uOXk=") + var validatorPubKey crypto.PubKeyEd25519 + copy(validatorPubKey[:], validatorPubKeyBytes) + + appHash, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") + + appState := AppState{ + FirstValidatorAddress: types.HexToAddress("Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f"), + InitialBalances: []Account{ + { + Address: types.HexToAddress("Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f"), + Balance: map[string]string{ + "MNT": helpers.BipToPip(big.NewInt(1000000000)).String(), + }, + }, + { + Address: types.HexToAddress("Mxfe60014a6e9ac91618f5d1cab3fd58cded61ee99"), + Balance: map[string]string{ + "MNT": helpers.BipToPip(big.NewInt(10000000)).String(), + }, + }, + }, + } + + appStateJSON, _ := json.Marshal(appState) + + genesis := tmtypes.GenesisDoc{ + GenesisTime: time.Date(2018, 7, 23, 0, 0, 0, 0, time.UTC), + ChainID: "minter-test-network-14", + ConsensusParams: nil, + Validators: []tmtypes.GenesisValidator{ + { + PubKey: validatorPubKey, + Power: 100, + }, + }, + AppHash: common.HexBytes(appHash), + AppState: json.RawMessage([]byte(appStateJSON)), + } + + genesis.ValidateAndComplete() + + return &genesis +} type AppState struct { FirstValidatorAddress types.Address `json:"first_validator_address"` diff --git a/gui/index.html b/gui/index.html new file mode 100644 index 000000000..5cfa29477 --- /dev/null +++ b/gui/index.html @@ -0,0 +1,221 @@ + + + + + + Minter Node GUI + + + + + + + + +
+ +
+ +
+
+
+
+
+
+ Node Info +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Moniker{{ status.node_info.moniker }}
Node ID{{ status.node_info.id }}
Listen Addr{{ status.node_info.listen_addr }}
Network ID{{ status.node_info.network }}
Minter Version{{ version }}
Tendermint Version{{ status.node_info.version }}
+
+
+
+ Net Info +
+ + + + + + + + + + + +
Is Listening
Connected Peers{{ net_info.n_peers }}
+
+
+
+
+
+ Syncing Info +
+ + + + + + + + + + + +
Is Synced + No + Yes +
Latest Block + #{{ status.sync_info.latest_block_height }} at {{ status.sync_info.latest_block_time }} +
+
+
+
+ Validator Info +
+ + + + + + + + + + + +
Public KeyMp{{ base64ToHex(status.validator_info.pub_key.value) }}
Voting Power{{ niceNum(status.validator_info.voting_power) }} of 100,000,000
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/helpers/helpers.go b/helpers/helpers.go index df492c909..37e959dba 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -1,6 +1,8 @@ package helpers -import "math/big" +import ( + "math/big" +) func BipToPip(bip *big.Int) *big.Int { p := big.NewInt(10) diff --git a/log/log.go b/log/log.go index 5dc2973f7..754891696 100644 --- a/log/log.go +++ b/log/log.go @@ -1,6 +1,7 @@ package log import ( + "github.com/tendermint/tendermint/libs/cli/flags" "github.com/tendermint/tendermint/libs/log" "os" ) @@ -10,7 +11,7 @@ var ( ) func init() { - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger, _ := flags.ParseLogLevel("consensus:info,state:info,*:error", log.NewTMLogger(os.Stdout), "info") SetLogger(logger) } @@ -22,6 +23,10 @@ func Info(msg string, ctx ...interface{}) { logger.Info(msg, ctx...) } +func Error(msg string, ctx ...interface{}) { + logger.Error(msg, ctx...) +} + func With(keyvals ...interface{}) log.Logger { return logger.With(keyvals...) } diff --git a/networks/testnet/config.toml b/networks/testnet/config.toml deleted file mode 100644 index 8026d56a9..000000000 --- a/networks/testnet/config.toml +++ /dev/null @@ -1,163 +0,0 @@ -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -##### main base config options ##### - -# TCP or UNIX socket address of the ABCI application, -# or the name of an ABCI application compiled in with the Tendermint binary -proxy_app = "tcp://127.0.0.1:46658" - -# A custom human readable name for this node -moniker = "MinterNode" - -# If this node is many blocks behind the tip of the chain, FastSync -# allows them to catchup quickly by downloading blocks in parallel -# and verifying their commits -fast_sync = true - -# Database backend: leveldb | memdb -db_backend = "leveldb" - -# Database directory -db_path = "data" - -# Output level for logging, including package level options -log_level = "main:info,state:info,*:error" - -##### additional base config options ##### - -# Path to the JSON file containing the initial validator set and other meta data -genesis_file = "config/genesis.json" - -# Path to the JSON file containing the private key to use as a validator in the consensus protocol -priv_validator_file = "config/priv_validator.json" - -# Path to the JSON file containing the private key to use for node authentication in the p2p protocol -node_key_file = "config/node_key.json" - -# Mechanism to connect to the ABCI application: socket | grpc -abci = "socket" - -# TCP or UNIX socket address for the profiling server to listen on -prof_laddr = "" - -# If true, query the ABCI app on connecting to a new peer -# so the app can decide if we should keep the connection or not -filter_peers = false - -##### advanced configuration options ##### - -##### rpc server configuration options ##### -[rpc] - -# TCP or UNIX socket address for the RPC server to listen on -laddr = "tcp://0.0.0.0:46657" - -# TCP or UNIX socket address for the gRPC server to listen on -# NOTE: This server only supports /broadcast_tx_commit -grpc_laddr = "" - -# Activate unsafe RPC commands like /dial_seeds and /unsafe_flush_mempool -unsafe = false - -##### peer to peer configuration options ##### -[p2p] - -# Address to listen for incoming connections -laddr = "tcp://0.0.0.0:46656" - -# Comma separated list of seed nodes to connect to -seeds = "" - -# Comma separated list of nodes to keep persistent connections to -persistent_peers = "249c62818bf4601605a65b5adc35278236bd5312@95.216.148.138:46656" - -# Path to address book -addr_book_file = "config/addrbook.json" - -# Set true for strict address routability rules -addr_book_strict = true - -# Time to wait before flushing messages out on the connection, in ms -flush_throttle_timeout = 100 - -# Maximum number of peers to connect to -max_num_peers = 50 - -# Maximum size of a message packet payload, in bytes -max_msg_packet_payload_size = 2048 - -# Rate at which packets can be sent, in bytes/second -send_rate = 1024000 - -# Rate at which packets can be received, in bytes/second -recv_rate = 1024000 - -# Set true to enable the peer-exchange reactor -pex = true - -# Seed mode, in which node constantly crawls the network and looks for -# peers. If another node asks it for addresses, it responds and disconnects. -# -# Does not work if the peer-exchange reactor is disabled. -seed_mode = false - -##### mempool configuration options ##### -[mempool] - -recheck = true -recheck_empty = false -broadcast = true -wal_dir = "data/mempool.wal" -cache_size = 100000 - -##### consensus configuration options ##### -[consensus] - -wal_file = "data/cs.wal/wal" - -# All timeouts are in milliseconds -timeout_propose = 3000 -timeout_propose_delta = 500 -timeout_prevote = 1000 -timeout_prevote_delta = 500 -timeout_precommit = 1000 -timeout_precommit_delta = 500 -timeout_commit = 5000 - -# Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) -skip_timeout_commit = false - -# BlockSize -max_block_size_txs = 10000 -max_block_size_bytes = 1 - -# EmptyBlocks mode and possible interval between empty blocks in seconds -create_empty_blocks = true -create_empty_blocks_interval = 0 - -# Reactor sleep duration parameters are in milliseconds -peer_gossip_sleep_duration = 100 -peer_query_maj23_sleep_duration = 2000 - -##### transactions indexer configuration options ##### -[tx_index] - -# What indexer to use for transactions -# -# Options: -# 1) "null" (default) -# 2) "kv" - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend). -indexer = "kv" - -# Comma-separated list of tags to index (by default the only tag is tx hash) -# -# It's recommended to index only a subset of tags due to possible memory -# bloat. This is, of course, depends on the indexer's DB and the volume of -# transactions. -index_tags = "" - -# When set to true, tells indexer to index all tags. Note this may be not -# desirable (see the comment above). IndexTags has a precedence over -# IndexAllTags (i.e. when given both, IndexTags will be indexed). -index_all_tags = true \ No newline at end of file diff --git a/networks/testnet/genesis.json b/networks/testnet/genesis.json deleted file mode 100644 index cf75e8a27..000000000 --- a/networks/testnet/genesis.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "genesis_time": "2018-06-09T00:00:00Z", - "chain_id": "minter-test-network-10", - "validators": [ - { - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": "qu4d3zD/VMkHFdkotWZS/FEb7Tci5Ylz6O+Ub12uOXk=" - }, - "power": "100", - "name": "" - } - ], - "app_state": { - "first_validator_address": "Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", - "initial_balances": [ - { - "address": "Mxa93163fdf10724dc4785ff5cbfb9ac0b5949409f", - "balance": { - "MNT": "10000000000000000000000000" - } - }, - { - "address": "Mxfe60014a6e9ac91618f5d1cab3fd58cded61ee99", - "balance": { - "MNT": "10000000000000000000000000" - } - } - ] - }, - "app_hash": "0000000000000000000000000000000000000000000000000000000000000000" -} \ No newline at end of file diff --git a/tests/acceptance/api/tests.go b/tests/acceptance/api/tests.go index b2bd811c5..991323808 100644 --- a/tests/acceptance/api/tests.go +++ b/tests/acceptance/api/tests.go @@ -32,11 +32,7 @@ func TestApiStatus() error { err = json.Unmarshal(data, &status) - if err != nil { - return err - } - - return nil + return err } func TestApiBlock() error { @@ -59,9 +55,5 @@ func TestApiBlock() error { err = json.Unmarshal(data, &blockResult) - if err != nil { - return err - } - - return nil + return err } diff --git a/tests/acceptance/docker/docker-compose.yml b/tests/acceptance/docker/docker-compose.yml index 5f23a56e5..b33434147 100644 --- a/tests/acceptance/docker/docker-compose.yml +++ b/tests/acceptance/docker/docker-compose.yml @@ -2,16 +2,7 @@ version: "3.4" services: minter: image: minterteam/minter:latest - command: --tendermint_addr=tcp://tendermint:46657 volumes: - ./data/.minter:/minter ports: - "8841:8841" - tendermint: - image: tendermint/tendermint:0.22.0 - command: node --proxy_app=tcp://minter:46658 - volumes: - - ./data/.tendermint:/tendermint - ports: - - "46656:46656" - - "46657:46657" diff --git a/tests/acceptance/main.go b/tests/acceptance/main.go index 8d568c13b..8c0dce2be 100644 --- a/tests/acceptance/main.go +++ b/tests/acceptance/main.go @@ -45,7 +45,7 @@ func main() { logger.Fatalf("Failed test \"%s\"\nReason: %s", testName, err) } - elapsed := time.Now().Sub(start) + elapsed := time.Since(start) logger.Printf("Completed \"%s\" in %s \n", testName, elapsed) } diff --git a/version/version.go b/version/version.go index 7c1c78936..fd87602e6 100755 --- a/version/version.go +++ b/version/version.go @@ -3,13 +3,13 @@ package version // Version components const ( Maj = "0" - Min = "0" - Fix = "6" + Min = "1" + Fix = "0" ) var ( // Must be a string because scripts like dist.sh read this file. - Version = "0.0.6" + Version = "0.1.0" // GitCommit is the current HEAD set using ldflags. GitCommit string