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