diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a730c36cd..6a09ad1ea 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,7 +34,7 @@ jobs: echo ::set-env name=DOCKER_REPO::$(if [[ "$SECRET_DOCKER_HUB_REPO" == "" ]]; then if [[ "$SECRET_DOCKER_HUB_USER" == "" ]]; then echo "testbuild"; else echo "$SECRET_DOCKER_HUB_USER"; fi; else echo "$SECRET_DOCKER_HUB_REPO"; fi) - name: Docker build - run: docker build -t $DOCKER_REPO/$DOCKER_IMAGE:$VERSION . + run: docker build -t $DOCKER_REPO/$DOCKER_IMAGE:$VERSION . -f ./Dockerfile-ci - name: Start docker container run: docker run -d --name $CONTAINER_NAME -p $API_RUN_PORT:8841 $DOCKER_REPO/$DOCKER_IMAGE:$VERSION diff --git a/CHANGELOG.md b/CHANGELOG.md index ebdc07c54..23d768990 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 1.2.0 + +- [core] Added ControlAddress for Candidate +- [core] Added changing candidate’s public key functionality +- [core] Coins now identified by ID, not by symbols +- [core] Added RecreateCoin tx +- [core] Added ChangeCoinOwner tx +- [core] Limit validators slots to 64 +- [core] Add EditMultisigData tx +- [core] Add PriceVoteData tx +- [core] Stake value calculation changes +- [console] Added PruneBlocks command +- [api] Marked as deprecated version of API v1 +- [api] Added Swagger UI for API v2 + ## 1.1.8 BUG FIXES diff --git a/Dockerfile-ci b/Dockerfile-ci new file mode 100644 index 000000000..c9ce18cf6 --- /dev/null +++ b/Dockerfile-ci @@ -0,0 +1,22 @@ +FROM tazhate/dockerfile-gox as builder + +COPY . /gopath/src/github.com/MinterTeam/minter-go-node +WORKDIR /gopath/src/github.com/MinterTeam/minter-go-node +RUN apt-get update && apt-get install libleveldb-dev -y --no-install-recommends -q +RUN make build + +FROM ubuntu:bionic + +COPY --from=builder /gopath/src/github.com/MinterTeam/minter-go-node/build/minter/ /usr/bin/minter +RUN apt update && apt install libleveldb1v5 ca-certificates -y --no-install-recommends -q && \ + addgroup minteruser && \ + useradd --no-log-init -r -m -d /minter -g minteruser minteruser && \ + chown -R minteruser:minteruser /minter && \ + rm -rf /var/lib/apt/lists/* + +USER minteruser +WORKDIR /minter +RUN mkdir /minter/data +EXPOSE 8841 +ENTRYPOINT ["/usr/bin/minter"] +CMD ["node", "--home-dir", "/minter", "--genesis", "https://github.com/MinterTeam/minter-go-node/releases/download/v1.2-testnet-9/genesis.json"] diff --git a/Makefile b/Makefile index b227735a9..e7cc4318a 100644 --- a/Makefile +++ b/Makefile @@ -44,12 +44,6 @@ update_tools: @echo "--> Updating tools" @go get -u $(GOTOOLS) -#Run this from CI -get_vendor_deps: - @rm -rf vendor/ - @echo "--> Running dep" - @go mod vendor - #Run this locally. ensure_deps: @rm -rf vendor/ diff --git a/api/address.go b/api/address.go index cf8d281a5..2322618df 100644 --- a/api/address.go +++ b/api/address.go @@ -5,8 +5,19 @@ import ( ) type AddressResponse struct { - Balance map[string]string `json:"balance"` - TransactionCount uint64 `json:"transaction_count"` + Balance []BalanceItem `json:"balances"` + TransactionCount uint64 `json:"transaction_count"` +} + +type BalanceItem struct { + CoinID uint32 `json:"coin_id"` + Symbol string `json:"symbol"` + Value string `json:"value"` +} + +type Coin struct { + ID uint32 `json:"id"` + Symbol string `json:"symbol"` } func Address(address types.Address, height int) (*AddressResponse, error) { @@ -18,19 +29,32 @@ func Address(address types.Address, height int) (*AddressResponse, error) { cState.RLock() defer cState.RUnlock() + balances := cState.Accounts().GetBalances(address) + response := AddressResponse{ - Balance: make(map[string]string), - TransactionCount: cState.Accounts.GetNonce(address), + Balance: make([]BalanceItem, len(balances)), + TransactionCount: cState.Accounts().GetNonce(address), } - balances := cState.Accounts.GetBalances(address) - - for k, v := range balances { - response.Balance[k.String()] = v.String() + isBaseCoinExists := false + for k, b := range balances { + response.Balance[k] = BalanceItem{ + CoinID: b.Coin.ID.Uint32(), + Symbol: b.Coin.GetFullSymbol(), + Value: b.Value.String(), + } + + if b.Coin.ID.IsBaseCoin() { + isBaseCoinExists = true + } } - if _, exists := response.Balance[types.GetBaseCoin().String()]; !exists { - response.Balance[types.GetBaseCoin().String()] = "0" + if !isBaseCoinExists { + response.Balance = append(response.Balance, BalanceItem{ + CoinID: types.GetBaseCoinID().Uint32(), + Symbol: types.GetBaseCoin().String(), + Value: "0", + }) } return &response, nil diff --git a/api/addresses.go b/api/addresses.go index 1f302e9c8..13940e303 100644 --- a/api/addresses.go +++ b/api/addresses.go @@ -5,9 +5,9 @@ import ( ) type AddressesResponse struct { - Address string `json:"address"` - Balance map[string]string `json:"balance"` - TransactionCount uint64 `json:"transaction_count"` + Address string `json:"address"` + Balance []BalanceItem `json:"balance"` + TransactionCount uint64 `json:"transaction_count"` } func Addresses(addresses []types.Address, height int) (*[]AddressesResponse, error) { @@ -22,19 +22,33 @@ func Addresses(addresses []types.Address, height int) (*[]AddressesResponse, err response := make([]AddressesResponse, len(addresses)) for i, address := range addresses { + balances := cState.Accounts().GetBalances(address) + data := AddressesResponse{ Address: address.String(), - Balance: make(map[string]string), - TransactionCount: cState.Accounts.GetNonce(address), + Balance: make([]BalanceItem, len(balances)), + TransactionCount: cState.Accounts().GetNonce(address), } - balances := cState.Accounts.GetBalances(address) - for k, v := range balances { - data.Balance[k.String()] = v.String() + isBaseCoinExists := false + for k, b := range balances { + data.Balance[k] = BalanceItem{ + CoinID: b.Coin.ID.Uint32(), + Symbol: b.Coin.GetFullSymbol(), + Value: b.Value.String(), + } + + if b.Coin.ID.IsBaseCoin() { + isBaseCoinExists = true + } } - if _, exists := data.Balance[types.GetBaseCoin().String()]; !exists { - data.Balance[types.GetBaseCoin().String()] = "0" + if !isBaseCoinExists { + data.Balance = append(data.Balance, BalanceItem{ + CoinID: types.GetBaseCoinID().Uint32(), + Symbol: types.GetBaseCoin().String(), + Value: "0", + }) } response[i] = data diff --git a/api/api.go b/api/api.go index acb5ab7d7..fb1cfc834 100644 --- a/api/api.go +++ b/api/api.go @@ -2,21 +2,14 @@ package api import ( "fmt" - eventsdb "github.com/MinterTeam/events-db" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/minter" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/rpc/lib/server" "github.com/rs/cors" "github.com/tendermint/go-amino" - "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/multisig" - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/tendermint/tendermint/evidence" "github.com/tendermint/tendermint/libs/log" - rpc "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/types" + rpc "github.com/tendermint/tendermint/rpc/client/local" "net/http" "net/url" "strings" @@ -34,7 +27,7 @@ var Routes = map[string]*rpcserver.RPCFunc{ "status": rpcserver.NewRPCFunc(Status, ""), "candidates": rpcserver.NewRPCFunc(Candidates, "height,include_stakes"), "candidate": rpcserver.NewRPCFunc(Candidate, "pub_key,height"), - "validators": rpcserver.NewRPCFunc(Validators, "height,page,perPage"), + "validators": rpcserver.NewRPCFunc(Validators, "height"), "address": rpcserver.NewRPCFunc(Address, "address,height"), "addresses": rpcserver.NewRPCFunc(Addresses, "addresses,height"), "send_transaction": rpcserver.NewRPCFunc(SendTransaction, "tx"), @@ -43,9 +36,9 @@ var Routes = map[string]*rpcserver.RPCFunc{ "block": rpcserver.NewRPCFunc(Block, "height"), "events": rpcserver.NewRPCFunc(Events, "height"), "net_info": rpcserver.NewRPCFunc(NetInfo, ""), - "coin_info": rpcserver.NewRPCFunc(CoinInfo, "symbol,height"), + "coin_info": rpcserver.NewRPCFunc(CoinInfo, "symbol,id,height"), "estimate_coin_sell": rpcserver.NewRPCFunc(EstimateCoinSell, "coin_to_sell,coin_to_buy,value_to_sell,height"), - "estimate_coin_sell_all": rpcserver.NewRPCFunc(EstimateCoinSellAll, "coin_to_sell,coin_to_buy,value_to_sell,gas_price,height"), + "estimate_coin_sell_all": rpcserver.NewRPCFunc(EstimateCoinSellAll, "coin_to_sell,coin_to_buy,value_to_sell,height"), "estimate_coin_buy": rpcserver.NewRPCFunc(EstimateCoinBuy, "coin_to_sell,coin_to_buy,value_to_buy,height"), "estimate_tx_commission": rpcserver.NewRPCFunc(EstimateTxCommission, "tx,height"), "unconfirmed_txs": rpcserver.NewRPCFunc(UnconfirmedTxs, "limit"), @@ -53,6 +46,7 @@ var Routes = map[string]*rpcserver.RPCFunc{ "min_gas_price": rpcserver.NewRPCFunc(MinGasPrice, ""), "genesis": rpcserver.NewRPCFunc(Genesis, ""), "missed_blocks": rpcserver.NewRPCFunc(MissedBlocks, "pub_key,height"), + "waitlist": rpcserver.NewRPCFunc(Waitlist, "pub_key,address,height"), } func responseTime(b *minter.Blockchain) func(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { @@ -60,17 +54,15 @@ func responseTime(b *minter.Blockchain) func(f func(http.ResponseWriter, *http.R return func(w http.ResponseWriter, r *http.Request) { start := time.Now() f(w, r) - go b.StatisticData().SetApiTime(time.Now().Sub(start), r.URL.Path) + go b.StatisticData().SetApiTime(time.Since(start), r.URL.Path) } } } -func RunAPI(b *minter.Blockchain, tmRPC *rpc.Local, cfg *config.Config, logger log.Logger) { +// RunAPI start +func RunAPI(codec *amino.Codec, b *minter.Blockchain, tmRPC *rpc.Local, cfg *config.Config, logger log.Logger) { + cdc = codec minterCfg = cfg - RegisterCryptoAmino(cdc) - eventsdb.RegisterAminoEvents(cdc) - RegisterEvidenceMessages(cdc) - client = tmRPC blockchain = b waitForTendermint() @@ -138,7 +130,7 @@ type Response struct { Log string `json:"log,omitempty"` } -func GetStateForHeight(height int) (*state.State, error) { +func GetStateForHeight(height int) (*state.CheckState, error) { if height > 0 { cState, err := blockchain.GetStateForHeight(uint64(height)) @@ -147,29 +139,3 @@ func GetStateForHeight(height int) (*state.State, error) { return blockchain.CurrentState(), nil } - -// RegisterAmino registers all crypto related types in the given (amino) codec. -func RegisterCryptoAmino(cdc *amino.Codec) { - // These are all written here instead of - cdc.RegisterInterface((*crypto.PubKey)(nil), nil) - cdc.RegisterConcrete(ed25519.PubKeyEd25519{}, - ed25519.PubKeyAminoName, nil) - cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, - secp256k1.PubKeyAminoName, nil) - cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{}, - multisig.PubKeyMultisigThresholdAminoRoute, nil) - - cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) - cdc.RegisterConcrete(ed25519.PrivKeyEd25519{}, - ed25519.PrivKeyAminoName, nil) - cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{}, - secp256k1.PrivKeyAminoName, nil) -} - -func RegisterEvidenceMessages(cdc *amino.Codec) { - cdc.RegisterInterface((*evidence.Message)(nil), nil) - cdc.RegisterConcrete(&evidence.ListMessage{}, - "tendermint/evidence/EvidenceListMessage", nil) - cdc.RegisterInterface((*types.Evidence)(nil), nil) - cdc.RegisterConcrete(&types.DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil) -} diff --git a/api/block.go b/api/block.go index c86855291..cc08ce71a 100644 --- a/api/block.go +++ b/api/block.go @@ -5,13 +5,15 @@ import ( "encoding/hex" "encoding/json" "fmt" + "time" + "github.com/MinterTeam/minter-go-node/core/rewards" "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/transaction/encoder" "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/rpc/lib/types" + rpctypes "github.com/MinterTeam/minter-go-node/rpc/lib/types" core_types "github.com/tendermint/tendermint/rpc/core/types" tmTypes "github.com/tendermint/tendermint/types" - "time" ) type BlockResponse struct { @@ -65,14 +67,18 @@ func Block(height int64) (*BlockResponse, error) { valHeight = 1 } - var totalValidators []*tmTypes.Validator - for i := 0; i < (((len(block.Block.LastCommit.Signatures) - 1) / 100) + 1); i++ { - tmValidators, err := client.Validators(&valHeight, i+1, 100) - if err != nil { - return nil, rpctypes.RPCError{Code: 500, Message: err.Error()} - } - totalValidators = append(totalValidators, tmValidators.Validators...) + tmValidators, err := client.Validators(&valHeight, 1, 100) + if err != nil { + return nil, rpctypes.RPCError{Code: 500, Message: err.Error()} } + totalValidators := tmValidators.Validators + + cState, err := GetStateForHeight(0) + if err != nil { + return nil, err + } + + txJsonEncoder := encoder.NewTxEncoderJSON(cState) txs := make([]BlockTransactionResponse, len(block.Block.Data.Txs)) for i, rawTx := range block.Block.Data.Txs { @@ -88,7 +94,7 @@ func Block(height int64) (*BlockResponse, error) { tags[string(tag.Key)] = string(tag.Value) } - data, err := encodeTxData(tx) + data, err := txJsonEncoder.EncodeData(tx) if err != nil { return nil, err } diff --git a/api/candidate.go b/api/candidate.go index 44b2b6f71..4172e4723 100644 --- a/api/candidate.go +++ b/api/candidate.go @@ -9,7 +9,7 @@ import ( type Stake struct { Owner string `json:"owner"` - Coin string `json:"coin"` + Coin Coin `json:"coin"` Value string `json:"value"` BipValue string `json:"bip_value"` } @@ -19,28 +19,31 @@ type CandidateResponse struct { OwnerAddress string `json:"owner_address"` TotalStake string `json:"total_stake"` PubKey string `json:"pub_key"` - Commission uint `json:"commission"` + Commission uint32 `json:"commission"` Stakes []Stake `json:"stakes,omitempty"` Status byte `json:"status"` } -func makeResponseCandidate(state *state.State, c candidates.Candidate, includeStakes bool) CandidateResponse { +func makeResponseCandidate(state *state.CheckState, c candidates.Candidate, includeStakes bool) CandidateResponse { candidate := CandidateResponse{ RewardAddress: c.RewardAddress.String(), OwnerAddress: c.OwnerAddress.String(), - TotalStake: state.Candidates.GetTotalStake(c.PubKey).String(), + TotalStake: state.Candidates().GetTotalStake(c.PubKey).String(), PubKey: c.PubKey.String(), Commission: c.Commission, Status: c.Status, } if includeStakes { - stakes := state.Candidates.GetStakes(c.PubKey) + stakes := state.Candidates().GetStakes(c.PubKey) candidate.Stakes = make([]Stake, len(stakes)) for i, stake := range stakes { candidate.Stakes[i] = Stake{ - Owner: stake.Owner.String(), - Coin: stake.Coin.String(), + Owner: stake.Owner.String(), + Coin: Coin{ + ID: stake.Coin.Uint32(), + Symbol: state.Coins().GetCoin(stake.Coin).GetFullSymbol(), + }, Value: stake.Value.String(), BipValue: stake.BipValue.String(), } @@ -58,15 +61,15 @@ func Candidate(pubkey types.Pubkey, height int) (*CandidateResponse, error) { if height != 0 { cState.Lock() - cState.Candidates.LoadCandidates() - cState.Candidates.LoadStakesOfCandidate(pubkey) + cState.Candidates().LoadCandidates() + cState.Candidates().LoadStakesOfCandidate(pubkey) cState.Unlock() } cState.RLock() defer cState.RUnlock() - candidate := cState.Candidates.GetCandidate(pubkey) + candidate := cState.Candidates().GetCandidate(pubkey) if candidate == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Candidate not found"} } diff --git a/api/candidates.go b/api/candidates.go index 3831471ad..50455b552 100644 --- a/api/candidates.go +++ b/api/candidates.go @@ -8,9 +8,9 @@ func Candidates(height int, includeStakes bool) (*[]CandidateResponse, error) { if height != 0 { cState.Lock() - cState.Candidates.LoadCandidates() + cState.Candidates().LoadCandidates() if includeStakes { - cState.Candidates.LoadStakes() + cState.Candidates().LoadStakes() } cState.Unlock() } @@ -18,7 +18,7 @@ func Candidates(height int, includeStakes bool) (*[]CandidateResponse, error) { cState.RLock() defer cState.RUnlock() - candidates := cState.Candidates.GetCandidates() + candidates := cState.Candidates().GetCandidates() result := make([]CandidateResponse, len(candidates)) for i, candidate := range candidates { diff --git a/api/coin_info.go b/api/coin_info.go index 748bc36e2..fae90461e 100644 --- a/api/coin_info.go +++ b/api/coin_info.go @@ -1,20 +1,25 @@ package api import ( + "github.com/MinterTeam/minter-go-node/core/state/coins" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/rpc/lib/types" ) type CoinInfoResponse struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - Volume string `json:"volume"` - Crr uint `json:"crr"` - ReserveBalance string `json:"reserve_balance"` - MaxSupply string `json:"max_supply"` + ID uint32 `json:"id"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Volume string `json:"volume"` + Crr uint32 `json:"crr"` + ReserveBalance string `json:"reserve_balance"` + MaxSupply string `json:"max_supply"` + OwnerAddress *string `json:"owner_address"` } -func CoinInfo(coinSymbol string, height int) (*CoinInfoResponse, error) { +func CoinInfo(coinSymbol *string, id *int, height int) (*CoinInfoResponse, error) { + var coin *coins.Model + cState, err := GetStateForHeight(height) if err != nil { return nil, err @@ -23,17 +28,39 @@ func CoinInfo(coinSymbol string, height int) (*CoinInfoResponse, error) { cState.RLock() defer cState.RUnlock() - coin := cState.Coins.GetCoin(types.StrToCoinSymbol(coinSymbol)) - if coin == nil { + if coinSymbol == nil && id == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin not found"} } + if coinSymbol != nil { + coin = cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(*coinSymbol), types.GetVersionFromSymbol(*coinSymbol)) + if coin == nil { + return nil, rpctypes.RPCError{Code: 404, Message: "Coin not found"} + } + } + + if id != nil { + coin = cState.Coins().GetCoin(types.CoinID(*id)) + if coin == nil { + return nil, rpctypes.RPCError{Code: 404, Message: "Coin not found"} + } + } + + var ownerAddress *string + info := cState.Coins().GetSymbolInfo(coin.Symbol()) + if info != nil && info.OwnerAddress() != nil { + owner := info.OwnerAddress().String() + ownerAddress = &owner + } + return &CoinInfoResponse{ + ID: coin.ID().Uint32(), Name: coin.Name(), - Symbol: coin.Symbol().String(), + Symbol: coin.GetFullSymbol(), Volume: coin.Volume().String(), Crr: coin.Crr(), ReserveBalance: coin.Reserve().String(), MaxSupply: coin.MaxSupply().String(), + OwnerAddress: ownerAddress, }, nil } diff --git a/api/estimate_coin_buy.go b/api/estimate_coin_buy.go index 1cd5681a8..a6caa9c49 100644 --- a/api/estimate_coin_buy.go +++ b/api/estimate_coin_buy.go @@ -10,12 +10,14 @@ import ( "math/big" ) +// EstimateCoinBuyResponse returns an estimate of buy coin transaction type EstimateCoinBuyResponse struct { WillPay string `json:"will_pay"` Commission string `json:"commission"` } -func EstimateCoinBuy(coinToSellString string, coinToBuyString string, valueToBuy *big.Int, height int) (*EstimateCoinBuyResponse, error) { +// EstimateCoinBuy returns an estimate of buy coin transaction +func EstimateCoinBuy(coinToSell, coinToBuy string, valueToBuy *big.Int, height int) (*EstimateCoinBuyResponse, error) { cState, err := GetStateForHeight(height) if err != nil { return nil, err @@ -24,56 +26,45 @@ func EstimateCoinBuy(coinToSellString string, coinToBuyString string, valueToBuy cState.RLock() defer cState.RUnlock() - coinToSell := types.StrToCoinSymbol(coinToSellString) - coinToBuy := types.StrToCoinSymbol(coinToBuyString) - - var result *big.Int - - if coinToSell == coinToBuy { - return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} - } - - if !cState.Coins.Exists(coinToSell) { + coinFrom := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToSell), types.GetVersionFromSymbol(coinToSell)) + if coinFrom == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to sell not exists"} } - if !cState.Coins.Exists(coinToBuy) { + coinTo := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToBuy), types.GetVersionFromSymbol(coinToBuy)) + if coinTo == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to buy not exists"} } + if coinFrom.ID() == coinTo.ID() { + return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} + } + commissionInBaseCoin := big.NewInt(commissions.ConvertTx) commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - if coinToSell != types.GetBaseCoin() { - coin := cState.Coins.GetCoin(coinToSell) - - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { + if !coinFrom.ID().IsBaseCoin() { + if coinFrom.Reserve().Cmp(commissionInBaseCoin) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), commissionInBaseCoin.String())} + coinFrom.Reserve().String(), commissionInBaseCoin.String())} } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), commissionInBaseCoin) } - switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) - result = formula.CalculatePurchaseAmount(coin.Volume(), coin.Reserve(), coin.Crr(), valueToBuy) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) + var result *big.Int - if coin.Reserve().Cmp(valueToBuy) < 0 { + switch { + case coinTo.ID().IsBaseCoin(): + if coinFrom.Reserve().Cmp(valueToBuy) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), valueToBuy.String())} + coinFrom.Reserve().String(), valueToBuy.String())} } - - result = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), valueToBuy) + result = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToBuy) + case coinFrom.ID().IsBaseCoin(): + result = formula.CalculatePurchaseAmount(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToBuy) default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) baseCoinNeeded := formula.CalculatePurchaseAmount(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToBuy) - if coinFrom.Reserve().Cmp(baseCoinNeeded) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coinFrom.Reserve().String(), baseCoinNeeded.String())} diff --git a/api/estimate_coin_sell.go b/api/estimate_coin_sell.go index dabdfdada..ca447962e 100644 --- a/api/estimate_coin_sell.go +++ b/api/estimate_coin_sell.go @@ -10,14 +10,14 @@ import ( "math/big" ) +// EstimateCoinSellResponse returns an estimate of sell coin transaction type EstimateCoinSellResponse struct { WillGet string `json:"will_get"` Commission string `json:"commission"` } -func EstimateCoinSell( - coinToSellString string, coinToBuyString string, valueToSell *big.Int, height int) (*EstimateCoinSellResponse, - error) { +// EstimateCoinSell returns an estimate of sell coin transaction +func EstimateCoinSell(coinToSell, coinToBuy string, valueToSell *big.Int, height int) (*EstimateCoinSellResponse, error) { cState, err := GetStateForHeight(height) if err != nil { return nil, err @@ -26,53 +26,46 @@ func EstimateCoinSell( cState.RLock() defer cState.RUnlock() - coinToSell := types.StrToCoinSymbol(coinToSellString) - coinToBuy := types.StrToCoinSymbol(coinToBuyString) - - var result *big.Int - - if coinToSell == coinToBuy { - return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} - } - - if !cState.Coins.Exists(coinToSell) { + coinFrom := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToSell), types.GetVersionFromSymbol(coinToSell)) + if coinFrom == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to sell not exists"} } - if !cState.Coins.Exists(coinToBuy) { + coinTo := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToBuy), types.GetVersionFromSymbol(coinToBuy)) + if coinTo == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to buy not exists"} } + if coinFrom.ID() == coinTo.ID() { + return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} + } + + var result *big.Int + commissionInBaseCoin := big.NewInt(commissions.ConvertTx) commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - if coinToSell != types.GetBaseCoin() { - coin := cState.Coins.GetCoin(coinToSell) - - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { + if !coinFrom.ID().IsBaseCoin() { + if coinFrom.Reserve().Cmp(commissionInBaseCoin) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), commissionInBaseCoin.String())} + coinFrom.Reserve().String(), commissionInBaseCoin.String())} } - if coin.Volume().Cmp(valueToSell) < 0 { + if coinFrom.Volume().Cmp(valueToSell) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin volume is not sufficient for transaction. Has: %s, required %s", - coin.Volume().String(), valueToSell.String())} + coinFrom.Volume().String(), valueToSell.String())} } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), commissionInBaseCoin) } switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) - result = formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) - result = formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) + case coinFrom.ID().IsBaseCoin(): + result = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToSell) + case coinTo.ID().IsBaseCoin(): + result = formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) result = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), basecoinValue) } diff --git a/api/estimate_coin_sell_all.go b/api/estimate_coin_sell_all.go index 45d138fb8..f42fdb654 100644 --- a/api/estimate_coin_sell_all.go +++ b/api/estimate_coin_sell_all.go @@ -9,12 +9,13 @@ import ( "math/big" ) +// EstimateCoinSellAllResponse returns an of sell all coin transaction type EstimateCoinSellAllResponse struct { WillGet string `json:"will_get"` } -func EstimateCoinSellAll( - coinToSellString string, coinToBuyString string, valueToSell *big.Int, gasPrice uint64, height int) (*EstimateCoinSellAllResponse, +// EstimateCoinSellAll returns an estimate of sell all coin transaction +func EstimateCoinSellAll(coinToSell, coinToBuy string, valueToSell *big.Int, height int) (*EstimateCoinSellAllResponse, error) { cState, err := GetStateForHeight(height) if err != nil { @@ -24,54 +25,43 @@ func EstimateCoinSellAll( cState.RLock() defer cState.RUnlock() - if gasPrice < 1 { - gasPrice = 1 - } - - coinToSell := types.StrToCoinSymbol(coinToSellString) - coinToBuy := types.StrToCoinSymbol(coinToBuyString) - - var result *big.Int - - if coinToSell == coinToBuy { - return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} - } - - if !cState.Coins.Exists(coinToSell) { + coinFrom := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToSell), types.GetVersionFromSymbol(coinToSell)) + if coinFrom == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to sell not exists"} } - if !cState.Coins.Exists(coinToBuy) { + coinTo := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(coinToBuy), types.GetVersionFromSymbol(coinToBuy)) + if coinTo == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Coin to buy not exists"} } + if coinFrom.ID() == coinTo.ID() { + return nil, rpctypes.RPCError{Code: 400, Message: "\"From\" coin equals to \"to\" coin"} + } + commissionInBaseCoin := big.NewInt(commissions.ConvertTx) commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) + var result *big.Int + switch { + case coinFrom.ID().IsBaseCoin(): valueToSell.Sub(valueToSell, commission) - if valueToSell.Cmp(big.NewInt(0)) != 1 { + if valueToSell.Sign() != 1 { return nil, rpctypes.RPCError{Code: 400, Message: "Not enough coins to pay commission"} } - result = formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) - result = formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) + result = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToSell) + case coinTo.ID().IsBaseCoin(): + result = formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) result.Sub(result, commission) if result.Cmp(big.NewInt(0)) != 1 { return nil, rpctypes.RPCError{Code: 400, Message: "Not enough coins to pay commission"} } default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) - basecoinValue.Sub(basecoinValue, commission) if basecoinValue.Cmp(big.NewInt(0)) != 1 { return nil, rpctypes.RPCError{Code: 400, Message: "Not enough coins to pay commission"} diff --git a/api/estimate_tx_commission.go b/api/estimate_tx_commission.go index 701dc4494..01e073c4b 100644 --- a/api/estimate_tx_commission.go +++ b/api/estimate_tx_commission.go @@ -30,7 +30,7 @@ func EstimateTxCommission(tx []byte, height int) (*TxCommissionResponse, error) commission := big.NewInt(0).Set(commissionInBaseCoin) if !decodedTx.GasCoin.IsBaseCoin() { - coin := cState.Coins.GetCoin(decodedTx.GasCoin) + coin := cState.Coins().GetCoin(decodedTx.GasCoin) if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { return nil, rpctypes.RPCError{Code: 400, Message: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", diff --git a/api/events.go b/api/events.go index b926debff..5de924b5f 100644 --- a/api/events.go +++ b/api/events.go @@ -1,7 +1,7 @@ package api import ( - eventsdb "github.com/MinterTeam/events-db" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" ) type EventsResponse struct { diff --git a/api/genesis.go b/api/genesis.go index cf07b4792..a8e8dabd7 100644 --- a/api/genesis.go +++ b/api/genesis.go @@ -1,8 +1,6 @@ package api -import ( - core_types "github.com/tendermint/tendermint/rpc/core/types" -) +import core_types "github.com/tendermint/tendermint/rpc/core/types" func Genesis() (*core_types.ResultGenesis, error) { return client.Genesis() diff --git a/api/maxgas.go b/api/maxgas.go index 2f2cbae4f..3154b5f6d 100644 --- a/api/maxgas.go +++ b/api/maxgas.go @@ -9,6 +9,6 @@ func MaxGas(height int) (*uint64, error) { cState.RLock() defer cState.RUnlock() - maxGas := cState.App.GetMaxGas() + maxGas := cState.App().GetMaxGas() return &maxGas, nil } diff --git a/api/missed_blocks.go b/api/missed_blocks.go index 146094ea1..3f0e7c324 100644 --- a/api/missed_blocks.go +++ b/api/missed_blocks.go @@ -18,14 +18,14 @@ func MissedBlocks(pubkey types.Pubkey, height int) (*MissedBlocksResponse, error if height != 0 { cState.Lock() - cState.Validators.LoadValidators() + cState.Validators().LoadValidators() cState.Unlock() } cState.RLock() defer cState.RUnlock() - vals := cState.Validators.GetValidators() + vals := cState.Validators().GetValidators() if vals == nil { return nil, rpctypes.RPCError{Code: 404, Message: "Validators not found"} } diff --git a/api/status.go b/api/status.go index ec7a52b54..8bc2af607 100644 --- a/api/status.go +++ b/api/status.go @@ -27,9 +27,9 @@ func Status() (*StatusResponse, error) { MinterVersion: version.Version, LatestBlockHash: fmt.Sprintf("%X", result.SyncInfo.LatestBlockHash), LatestAppHash: fmt.Sprintf("%X", result.SyncInfo.LatestAppHash), + KeepLastStates: minterCfg.BaseConfig.KeepLastStates, LatestBlockHeight: result.SyncInfo.LatestBlockHeight, LatestBlockTime: result.SyncInfo.LatestBlockTime, - KeepLastStates: minterCfg.BaseConfig.KeepLastStates, TmStatus: result, }, nil } diff --git a/api/transaction.go b/api/transaction.go index 3997a575d..4aafd4312 100644 --- a/api/transaction.go +++ b/api/transaction.go @@ -1,81 +1,29 @@ package api import ( - "fmt" + "encoding/json" + "github.com/MinterTeam/minter-go-node/core/transaction" - "github.com/MinterTeam/minter-go-node/rpc/lib/types" - "github.com/tendermint/tendermint/libs/bytes" + "github.com/MinterTeam/minter-go-node/core/transaction/encoder" ) -func Transaction(hash []byte) (*TransactionResponse, error) { +func Transaction(hash []byte) (json.RawMessage, error) { tx, err := client.Tx(hash, false) if err != nil { return nil, err } decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) - sender, _ := decodedTx.Sender() - - tags := make(map[string]string) - for _, tag := range tx.TxResult.Events[0].Attributes { - tags[string(tag.Key)] = string(tag.Value) - } - data, err := encodeTxData(decodedTx) + cState, err := GetStateForHeight(0) if err != nil { return nil, err } - return &TransactionResponse{ - Hash: bytes.HexBytes(tx.Tx.Hash()).String(), - RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), - Height: tx.Height, - Index: tx.Index, - From: sender.String(), - Nonce: decodedTx.Nonce, - GasPrice: decodedTx.GasPrice, - GasCoin: decodedTx.GasCoin.String(), - Gas: decodedTx.Gas(), - Type: uint8(decodedTx.Type), - Data: data, - Payload: decodedTx.Payload, - Tags: tags, - Code: tx.TxResult.Code, - Log: tx.TxResult.Log, - }, nil -} + cState.RLock() + defer cState.RUnlock() -func encodeTxData(decodedTx *transaction.Transaction) ([]byte, error) { - switch decodedTx.Type { - case transaction.TypeSend: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SendData)) - case transaction.TypeRedeemCheck: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.RedeemCheckData)) - case transaction.TypeSellCoin: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SellCoinData)) - case transaction.TypeSellAllCoin: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SellAllCoinData)) - case transaction.TypeBuyCoin: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.BuyCoinData)) - case transaction.TypeCreateCoin: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.CreateCoinData)) - case transaction.TypeDeclareCandidacy: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.DeclareCandidacyData)) - case transaction.TypeDelegate: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.DelegateData)) - case transaction.TypeSetCandidateOnline: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SetCandidateOnData)) - case transaction.TypeSetCandidateOffline: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SetCandidateOffData)) - case transaction.TypeUnbond: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.UnbondData)) - case transaction.TypeMultisend: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.MultisendData)) - case transaction.TypeCreateMultisig: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.CreateMultisigData)) - case transaction.TypeEditCandidate: - return cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.EditCandidateData)) - } + txJsonEncoder := encoder.NewTxEncoderJSON(cState) - return nil, rpctypes.RPCError{Code: 500, Message: "unknown tx type"} + return txJsonEncoder.Encode(decodedTx, tx) } diff --git a/api/transactions.go b/api/transactions.go index 2b79cd8e8..ac8dc9bde 100644 --- a/api/transactions.go +++ b/api/transactions.go @@ -2,9 +2,9 @@ package api import ( "encoding/json" - "fmt" + "github.com/MinterTeam/minter-go-node/core/transaction" - "github.com/tendermint/tendermint/libs/bytes" + "github.com/MinterTeam/minter-go-node/core/transaction/encoder" core_types "github.com/tendermint/tendermint/rpc/core/types" ) @@ -17,7 +17,7 @@ type TransactionResponse struct { Nonce uint64 `json:"nonce"` Gas int64 `json:"gas"` GasPrice uint32 `json:"gas_price"` - GasCoin string `json:"gas_coin"` + GasCoin Coin `json:"gas_coin"` Type uint8 `json:"type"` Data json.RawMessage `json:"data"` Payload []byte `json:"payload"` @@ -31,7 +31,7 @@ type ResultTxSearch struct { TotalCount int `json:"total_count"` } -func Transactions(query string, page, perPage int) (*[]TransactionResponse, error) { +func Transactions(query string, page, perPage int) (*[]json.RawMessage, error) { if page == 0 { page = 1 } @@ -44,38 +44,24 @@ func Transactions(query string, page, perPage int) (*[]TransactionResponse, erro return nil, err } - result := make([]TransactionResponse, len(rpcResult.Txs)) - for i, tx := range rpcResult.Txs { - decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) - sender, _ := decodedTx.Sender() + cState, err := GetStateForHeight(0) + if err != nil { + return nil, err + } - tags := make(map[string]string) - for _, tag := range tx.TxResult.Events[0].Attributes { - tags[string(tag.Key)] = string(tag.Value) - } + cState.RLock() + defer cState.RUnlock() - data, err := encodeTxData(decodedTx) + result := make([]json.RawMessage, 0, len(rpcResult.Txs)) + for _, tx := range rpcResult.Txs { + decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) + txJsonEncoder := encoder.NewTxEncoderJSON(cState) + response, err := txJsonEncoder.Encode(decodedTx, tx) if err != nil { return nil, err } - result[i] = TransactionResponse{ - Hash: bytes.HexBytes(tx.Tx.Hash()).String(), - RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), - Height: tx.Height, - Index: tx.Index, - From: sender.String(), - Nonce: decodedTx.Nonce, - Gas: decodedTx.Gas(), - GasPrice: decodedTx.GasPrice, - GasCoin: decodedTx.GasCoin.String(), - Type: uint8(decodedTx.Type), - Data: data, - Payload: decodedTx.Payload, - Tags: tags, - Code: tx.TxResult.Code, - Log: tx.TxResult.Log, - } + result = append(result, response) } return &result, nil diff --git a/api/v2/service/address.go b/api/v2/service/address.go index 4b0a54938..dbcb68e88 100644 --- a/api/v2/service/address.go +++ b/api/v2/service/address.go @@ -3,46 +3,177 @@ package service import ( "context" "encoding/hex" - "fmt" + "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/formula" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "math/big" + "strings" ) -func (s *Service) Address(_ context.Context, req *pb.AddressRequest) (*pb.AddressResponse, error) { - if len(req.Address) < 3 { - return new(pb.AddressResponse), status.Error(codes.InvalidArgument, "invalid address") +type stakeUser struct { + Value *big.Int + BipValue *big.Int +} + +// Address returns coins list, balance and transaction count of an address. +func (s *Service) Address(ctx context.Context, req *pb.AddressRequest) (*pb.AddressResponse, error) { + if !strings.HasPrefix(strings.Title(req.Address), "Mx") { + return nil, status.Error(codes.InvalidArgument, "invalid address") } decodeString, err := hex.DecodeString(req.Address[2:]) if err != nil { - return new(pb.AddressResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, "invalid address") } - cState, err := s.getStateForHeight(req.Height) + address := types.BytesToAddress(decodeString) + + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.AddressResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) + } + + if req.Height != 0 && req.Delegated { + cState.Lock() + cState.Candidates().LoadCandidates() + cState.Candidates().LoadStakes() + cState.Unlock() } cState.RLock() defer cState.RUnlock() - address := types.BytesToAddress(decodeString) - response := &pb.AddressResponse{ - Balance: make(map[string]string), - TransactionsCount: fmt.Sprintf("%d", cState.Accounts.GetNonce(address)), + balances := cState.Accounts().GetBalances(address) + var res pb.AddressResponse + + totalStakesGroupByCoin := map[types.CoinID]*big.Int{} + + res.Balance = make([]*pb.AddressBalance, 0, len(balances)) + for _, coin := range balances { + totalStakesGroupByCoin[coin.Coin.ID] = coin.Value + res.Balance = append(res.Balance, &pb.AddressBalance{ + Coin: &pb.Coin{ + Id: uint64(coin.Coin.ID), + Symbol: cState.Coins().GetCoin(coin.Coin.ID).GetFullSymbol(), + }, + Value: coin.Value.String(), + BipValue: customCoinBipBalance(coin.Coin.ID, coin.Value, cState).String(), + }) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } - balances := cState.Accounts.GetBalances(address) + if req.Delegated { + var userDelegatedStakesGroupByCoin = map[types.CoinID]*stakeUser{} + allCandidates := cState.Candidates().GetCandidates() + for _, candidate := range allCandidates { + userStakes := userStakes(candidate.PubKey, address, cState) + for coin, userStake := range userStakes { + stake, ok := userDelegatedStakesGroupByCoin[coin] + if !ok { + stake = &stakeUser{ + Value: big.NewInt(0), + BipValue: big.NewInt(0), + } + } + stake.Value.Add(stake.Value, userStake.Value) + stake.BipValue.Add(stake.BipValue, userStake.BipValue) + userDelegatedStakesGroupByCoin[coin] = stake + } + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + res.Delegated = make([]*pb.AddressDelegatedBalance, 0, len(userDelegatedStakesGroupByCoin)) + for coinID, delegatedStake := range userDelegatedStakesGroupByCoin { + res.Delegated = append(res.Delegated, &pb.AddressDelegatedBalance{ + Coin: &pb.Coin{ + Id: uint64(coinID), + Symbol: cState.Coins().GetCoin(coinID).GetFullSymbol(), + }, + Value: delegatedStake.Value.String(), + DelegateBipValue: delegatedStake.BipValue.String(), + BipValue: customCoinBipBalance(coinID, delegatedStake.Value, cState).String(), + }) + + totalStake, ok := totalStakesGroupByCoin[coinID] + if !ok { + totalStake = big.NewInt(0) + totalStakesGroupByCoin[coinID] = totalStake + } + totalStake.Add(totalStake, delegatedStake.Value) + } + } - for k, v := range balances { - response.Balance[k.String()] = v.String() + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } - if _, exists := response.Balance[types.GetBaseCoin().String()]; !exists { - response.Balance[types.GetBaseCoin().String()] = "0" + coinsBipValue := big.NewInt(0) + res.Total = make([]*pb.AddressBalance, 0, len(totalStakesGroupByCoin)) + for coinID, stake := range totalStakesGroupByCoin { + balance := customCoinBipBalance(coinID, stake, cState) + if req.Delegated { + res.Total = append(res.Total, &pb.AddressBalance{ + Coin: &pb.Coin{ + Id: uint64(coinID), + Symbol: cState.Coins().GetCoin(coinID).GetFullSymbol(), + }, + Value: stake.String(), + BipValue: balance.String(), + }) + } + coinsBipValue.Add(coinsBipValue, balance) + } + res.BipValue = coinsBipValue.String() + res.TransactionCount = cState.Accounts().GetNonce(address) + return &res, nil +} + +func customCoinBipBalance(coinToSell types.CoinID, valueToSell *big.Int, cState *state.CheckState) *big.Int { + coinToBuy := types.GetBaseCoinID() + + if coinToSell == coinToBuy { + return valueToSell + } + + if coinToSell.IsBaseCoin() { + coin := cState.Coins().GetCoin(coinToBuy) + return formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) + } + + if coinToBuy.IsBaseCoin() { + coin := cState.Coins().GetCoin(coinToSell) + return formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) + } + + coinFrom := cState.Coins().GetCoin(coinToSell) + coinTo := cState.Coins().GetCoin(coinToBuy) + basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) + return formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), basecoinValue) +} + +func userStakes(c types.Pubkey, address types.Address, state *state.CheckState) map[types.CoinID]*stakeUser { + var userStakes = map[types.CoinID]*stakeUser{} + + stakes := state.Candidates().GetStakes(c) + + for _, stake := range stakes { + if stake.Owner != address { + continue + } + userStakes[stake.Coin] = &stakeUser{ + Value: stake.Value, + BipValue: stake.BipValue, + } } - return response, nil + return userStakes } diff --git a/api/v2/service/addresses.go b/api/v2/service/addresses.go index b54f2428d..b189135dd 100644 --- a/api/v2/service/addresses.go +++ b/api/v2/service/addresses.go @@ -8,47 +8,136 @@ import ( pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "math/big" ) -func (s *Service) Addresses(_ context.Context, req *pb.AddressesRequest) (*pb.AddressesResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// Addresses returns list of addresses. +func (s *Service) Addresses(ctx context.Context, req *pb.AddressesRequest) (*pb.AddressesResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.AddressesResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) + } + + if req.Height != 0 && req.Delegated { + cState.Lock() + cState.Candidates().LoadCandidates() + cState.Candidates().LoadStakes() + cState.Unlock() } cState.RLock() defer cState.RUnlock() response := &pb.AddressesResponse{ - Addresses: make([]*pb.AddressesResponse_Result, 0, len(req.Addresses)), + Addresses: make(map[string]*pb.AddressesResponse_Result, len(req.Addresses)), } - for _, address := range req.Addresses { - if len(address) < 3 { - return new(pb.AddressesResponse), status.Error(codes.InvalidArgument, fmt.Sprintf("invalid address %s", address)) + for _, addr := range req.Addresses { + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return response, timeoutStatus.Err() + } + + if len(addr) < 3 { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid address %s", addr)) } - decodeString, err := hex.DecodeString(address[2:]) + decodeString, err := hex.DecodeString(addr[2:]) if err != nil { - return new(pb.AddressesResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, err.Error()) } - addr := types.BytesToAddress(decodeString) - data := &pb.AddressesResponse_Result{ - Address: address, - Balance: make(map[string]string), - TransactionsCount: fmt.Sprintf("%d", cState.Accounts.GetNonce(addr)), + address := types.BytesToAddress(decodeString) + + balances := cState.Accounts().GetBalances(address) + var res pb.AddressesResponse_Result + + totalStakesGroupByCoin := map[types.CoinID]*big.Int{} + + res.Balance = make([]*pb.AddressBalance, 0, len(balances)) + for _, coin := range balances { + totalStakesGroupByCoin[coin.Coin.ID] = coin.Value + res.Balance = append(res.Balance, &pb.AddressBalance{ + Coin: &pb.Coin{ + Id: uint64(coin.Coin.ID), + Symbol: cState.Coins().GetCoin(coin.Coin.ID).GetFullSymbol(), + }, + Value: coin.Value.String(), + BipValue: customCoinBipBalance(coin.Coin.ID, coin.Value, cState).String(), + }) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + if req.Delegated { + var userDelegatedStakesGroupByCoin = map[types.CoinID]*stakeUser{} + allCandidates := cState.Candidates().GetCandidates() + for _, candidate := range allCandidates { + userStakes := userStakes(candidate.PubKey, address, cState) + for coin, userStake := range userStakes { + stake, ok := userDelegatedStakesGroupByCoin[coin] + if !ok { + stake = &stakeUser{ + Value: big.NewInt(0), + BipValue: big.NewInt(0), + } + } + stake.Value.Add(stake.Value, userStake.Value) + stake.BipValue.Add(stake.BipValue, userStake.BipValue) + userDelegatedStakesGroupByCoin[coin] = stake + } + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + res.Delegated = make([]*pb.AddressDelegatedBalance, 0, len(userDelegatedStakesGroupByCoin)) + for coinID, delegatedStake := range userDelegatedStakesGroupByCoin { + res.Delegated = append(res.Delegated, &pb.AddressDelegatedBalance{ + Coin: &pb.Coin{ + Id: uint64(coinID), + Symbol: cState.Coins().GetCoin(coinID).GetFullSymbol(), + }, + Value: delegatedStake.Value.String(), + DelegateBipValue: delegatedStake.BipValue.String(), + BipValue: customCoinBipBalance(coinID, delegatedStake.Value, cState).String(), + }) + + totalStake, ok := totalStakesGroupByCoin[coinID] + if !ok { + totalStake = big.NewInt(0) + totalStakesGroupByCoin[coinID] = totalStake + } + totalStake.Add(totalStake, delegatedStake.Value) + } } - balances := cState.Accounts.GetBalances(addr) - for k, v := range balances { - data.Balance[k.String()] = v.String() + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } - if _, exists := data.Balance[types.GetBaseCoin().String()]; !exists { - data.Balance[types.GetBaseCoin().String()] = "0" + coinsBipValue := big.NewInt(0) + res.Total = make([]*pb.AddressBalance, 0, len(totalStakesGroupByCoin)) + for coinID, stake := range totalStakesGroupByCoin { + balance := customCoinBipBalance(coinID, stake, cState) + if req.Delegated { + res.Total = append(res.Total, &pb.AddressBalance{ + Coin: &pb.Coin{ + Id: uint64(coinID), + Symbol: cState.Coins().GetCoin(coinID).GetFullSymbol(), + }, + Value: stake.String(), + BipValue: balance.String(), + }) + } + coinsBipValue.Add(coinsBipValue, balance) } + res.BipValue = coinsBipValue.String() + res.TransactionCount = cState.Accounts().GetNonce(address) - response.Addresses = append(response.Addresses, data) + response.Addresses[addr] = &res } return response, nil diff --git a/api/v2/service/block.go b/api/v2/service/block.go index a443e24cd..9dab7526b 100644 --- a/api/v2/service/block.go +++ b/api/v2/service/block.go @@ -6,137 +6,271 @@ import ( "encoding/hex" "fmt" "github.com/MinterTeam/minter-go-node/core/rewards" + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/state/coins" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + _struct "github.com/golang/protobuf/ptypes/struct" + "github.com/tendermint/iavl" core_types "github.com/tendermint/tendermint/rpc/core/types" tmTypes "github.com/tendermint/tendermint/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "strings" "time" ) -func (s *Service) Block(_ context.Context, req *pb.BlockRequest) (*pb.BlockResponse, error) { - block, err := s.client.Block(&req.Height) +// Block returns block data at given height. +func (s *Service) Block(ctx context.Context, req *pb.BlockRequest) (*pb.BlockResponse, error) { + height := int64(req.Height) + block, err := s.client.Block(&height) if err != nil { - return new(pb.BlockResponse), status.Error(codes.NotFound, "Block not found") + return nil, status.Error(codes.NotFound, "Block not found") } - blockResults, err := s.client.BlockResults(&req.Height) + blockResults, err := s.client.BlockResults(&height) if err != nil { - return new(pb.BlockResponse), status.Error(codes.NotFound, "Block results not found") + return nil, status.Error(codes.NotFound, "Block results not found") } - valHeight := req.Height - 1 + valHeight := height - 1 if valHeight < 1 { valHeight = 1 } + response := &pb.BlockResponse{ + Hash: hex.EncodeToString(block.Block.Hash()), + Height: uint64(block.Block.Height), + Time: block.Block.Time.Format(time.RFC3339Nano), + TransactionCount: uint64(len(block.Block.Txs)), + } + var totalValidators []*tmTypes.Validator - for i := 0; i < (((len(block.Block.LastCommit.Signatures) - 1) / 100) + 1); i++ { - tmValidators, err := s.client.Validators(&valHeight, i+1, 100) + + if len(req.Fields) == 0 { + response.Size = uint64(len(s.cdc.MustMarshalBinaryLengthPrefixed(block))) + response.BlockReward = rewards.GetRewardForBlock(uint64(height)).String() + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + currentState := s.blockchain.CurrentState() + currentState.RLock() + defer currentState.RUnlock() + + response.Transactions, err = s.blockTransaction(block, blockResults, currentState.Coins()) if err != nil { - return new(pb.BlockResponse), status.Error(codes.Internal, err.Error()) + return nil, err } - totalValidators = append(totalValidators, tmValidators.Validators...) - } - txs := make([]*pb.BlockResponse_Transaction, 0, len(block.Block.Data.Txs)) - for i, rawTx := range block.Block.Data.Txs { - tx, _ := transaction.TxDecoder.DecodeFromBytes(rawTx) - sender, _ := tx.Sender() + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } - tags := make(map[string]string) - for _, tag := range blockResults.TxsResults[i].Events[0].Attributes { - tags[string(tag.Key)] = string(tag.Value) + tmValidators, err := s.client.Validators(&valHeight, 1, 100) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) } + totalValidators = tmValidators.Validators - dataStruct, err := s.encodeTxData(tx) + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + response.Proposer, err = blockProposer(block, totalValidators) if err != nil { - return new(pb.BlockResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, err } - txs = append(txs, &pb.BlockResponse_Transaction{ - Hash: fmt.Sprintf("Mt%x", rawTx.Hash()), - RawTx: fmt.Sprintf("%x", []byte(rawTx)), - From: sender.String(), - Nonce: fmt.Sprintf("%d", tx.Nonce), - GasPrice: fmt.Sprintf("%d", tx.GasPrice), - Type: fmt.Sprintf("%d", tx.Type), - Data: dataStruct, - Payload: tx.Payload, - ServiceData: tx.ServiceData, - Gas: fmt.Sprintf("%d", tx.Gas()), - GasCoin: tx.GasCoin.String(), - Tags: tags, - Code: fmt.Sprintf("%d", blockResults.TxsResults[i].Code), - Log: blockResults.TxsResults[i].Log, - }) - } + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + response.Validators = blockValidators(totalValidators, block) - var validators []*pb.BlockResponse_Validator - var proposer string - if req.Height > 1 { - p, err := getBlockProposer(block, totalValidators) + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + response.Evidence, err = blockEvidence(block) if err != nil { - return new(pb.BlockResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, err + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + cStateOld, err := s.blockchain.GetStateForHeight(uint64(height)) + if err != iavl.ErrVersionDoesNotExist && err != nil { + return nil, status.Error(codes.Internal, err.Error()) } - if p != nil { - str := p.String() - proposer = str + if err != iavl.ErrVersionDoesNotExist { + response.Missed = missedBlockValidators(cStateOld) } - validators = make([]*pb.BlockResponse_Validator, 0, len(totalValidators)) - for _, tmval := range totalValidators { - signed := false - for _, vote := range block.Block.LastCommit.Signatures { - if bytes.Equal(vote.ValidatorAddress.Bytes(), tmval.Address.Bytes()) { - signed = true - break + return response, nil + } + + for _, field := range req.Fields { + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + switch field { + case pb.BlockRequest_size: + response.Size = uint64(len(s.cdc.MustMarshalBinaryLengthPrefixed(block))) + case pb.BlockRequest_block_reward: + response.BlockReward = rewards.GetRewardForBlock(uint64(height)).String() + case pb.BlockRequest_transactions: + cState := s.blockchain.CurrentState() + + response.Transactions, err = s.blockTransaction(block, blockResults, cState.Coins()) + if err != nil { + return nil, err + } + case pb.BlockRequest_missed: + cStateOld, err := s.blockchain.GetStateForHeight(uint64(height)) + if err != iavl.ErrVersionDoesNotExist && err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + if err != iavl.ErrVersionDoesNotExist { + response.Missed = missedBlockValidators(cStateOld) + } + + case pb.BlockRequest_proposer, pb.BlockRequest_validators: + if len(totalValidators) == 0 { + tmValidators, err := s.client.Validators(&valHeight, 1, 100) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) } + totalValidators = tmValidators.Validators + } + + if pb.BlockRequest_validators == field { + response.Validators = blockValidators(totalValidators, block) + continue } - validators = append(validators, &pb.BlockResponse_Validator{ - PublicKey: fmt.Sprintf("Mp%x", tmval.PubKey.Bytes()[5:]), - Signed: signed, - }) + response.Proposer, err = blockProposer(block, totalValidators) + if err != nil { + return nil, err + } + case pb.BlockRequest_evidence: + response.Evidence, err = blockEvidence(block) + if err != nil { + return nil, err + } } + } - evidences := make([]*pb.BlockResponse_Evidence_Evidence, len(block.Block.Evidence.Evidence)) + return response, nil +} + +func blockEvidence(block *core_types.ResultBlock) (*pb.BlockResponse_Evidence, error) { + evidences := make([]*_struct.Struct, 0, len(block.Block.Evidence.Evidence)) for _, evidence := range block.Block.Evidence.Evidence { - evidences = append(evidences, &pb.BlockResponse_Evidence_Evidence{ - Height: fmt.Sprintf("%d", evidence.Height()), - Time: evidence.Time().Format(time.RFC3339Nano), - Address: fmt.Sprintf("%s", evidence.Address()), - Hash: fmt.Sprintf("%s", evidence.Hash()), + proto, err := tmTypes.EvidenceToProto(evidence) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + str, err := toStruct(proto.GetSum()) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + evidences = append(evidences, str) + } + return &pb.BlockResponse_Evidence{Evidence: evidences}, nil +} + +func blockValidators(totalValidators []*tmTypes.Validator, block *core_types.ResultBlock) []*pb.BlockResponse_Validator { + validators := make([]*pb.BlockResponse_Validator, 0, len(totalValidators)) + for _, tmval := range totalValidators { + signed := false + for _, vote := range block.Block.LastCommit.Signatures { + if bytes.Equal(vote.ValidatorAddress.Bytes(), tmval.Address.Bytes()) { + signed = true + break + } + } + validators = append(validators, &pb.BlockResponse_Validator{ + PublicKey: fmt.Sprintf("Mp%x", tmval.PubKey.Bytes()[5:]), + Signed: signed, + }) + } + + return validators +} + +func missedBlockValidators(s *state.CheckState) []string { + var missedBlocks []string + for _, val := range s.Validators().GetValidators() { + missedBlocks = append(missedBlocks, val.AbsentTimes.String()) + } + + return missedBlocks +} + +func blockProposer(block *core_types.ResultBlock, totalValidators []*tmTypes.Validator) (string, error) { + p := getBlockProposer(block, totalValidators) + if p != nil { + return p.String(), nil + } + return "", nil +} + +func (s *Service) blockTransaction(block *core_types.ResultBlock, blockResults *core_types.ResultBlockResults, coins coins.RCoins) ([]*pb.BlockResponse_Transaction, error) { + txs := make([]*pb.BlockResponse_Transaction, 0, len(block.Block.Data.Txs)) + + for i, rawTx := range block.Block.Data.Txs { + tx, _ := transaction.TxDecoder.DecodeFromBytes(rawTx) + sender, _ := tx.Sender() + + tags := make(map[string]string) + for _, tag := range blockResults.TxsResults[i].Events[0].Attributes { + tags[string(tag.Key)] = string(tag.Value) + } + + data, err := encode(tx.GetDecodedData(), coins) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + txs = append(txs, &pb.BlockResponse_Transaction{ + Hash: strings.Title(fmt.Sprintf("Mt%x", rawTx.Hash())), + RawTx: fmt.Sprintf("%x", []byte(rawTx)), + From: sender.String(), + Nonce: tx.Nonce, + GasPrice: uint64(tx.GasPrice), + Type: uint64(tx.Type), + Data: data, + Payload: tx.Payload, + ServiceData: tx.ServiceData, + Gas: uint64(tx.Gas()), + GasCoin: &pb.Coin{ + Id: uint64(tx.GasCoin), + Symbol: coins.GetCoin(tx.GasCoin).GetFullSymbol(), + }, + Tags: tags, + Code: uint64(blockResults.TxsResults[i].Code), + Log: blockResults.TxsResults[i].Log, }) } - return &pb.BlockResponse{ - Hash: hex.EncodeToString(block.Block.Hash()), - Height: fmt.Sprintf("%d", block.Block.Height), - Time: block.Block.Time.Format(time.RFC3339Nano), - TransactionsCount: fmt.Sprintf("%d", len(block.Block.Txs)), - Transactions: txs, - BlockReward: rewards.GetRewardForBlock(uint64(req.Height)).String(), - Size: fmt.Sprintf("%d", len(s.cdc.MustMarshalBinaryLengthPrefixed(block))), - Proposer: proposer, - Validators: validators, - Evidence: &pb.BlockResponse_Evidence{ - Evidence: evidences, - }, - }, nil + return txs, nil } -func getBlockProposer(block *core_types.ResultBlock, vals []*tmTypes.Validator) (*types.Pubkey, error) { +func getBlockProposer(block *core_types.ResultBlock, vals []*tmTypes.Validator) *types.Pubkey { for _, tmval := range vals { if bytes.Equal(tmval.Address.Bytes(), block.Block.ProposerAddress.Bytes()) { var result types.Pubkey copy(result[:], tmval.PubKey.Bytes()[5:]) - return &result, nil + return &result } } - return nil, status.Error(codes.NotFound, "Block proposer not found") + return nil } diff --git a/api/v2/service/candidate.go b/api/v2/service/candidate.go index 1255796a5..1b77ccaea 100644 --- a/api/v2/service/candidate.go +++ b/api/v2/service/candidate.go @@ -3,70 +3,90 @@ package service import ( "context" "encoding/hex" - "fmt" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" + "math/big" ) -func (s *Service) Candidate(_ context.Context, req *pb.CandidateRequest) (*pb.CandidateResponse, error) { +// Candidate returns candidate’s info by provided public_key. It will respond with 404 code if candidate is not found. +func (s *Service) Candidate(ctx context.Context, req *pb.CandidateRequest) (*pb.CandidateResponse, error) { if len(req.PublicKey) < 3 { - return new(pb.CandidateResponse), status.Error(codes.InvalidArgument, "invalid public_key") + return nil, status.Error(codes.InvalidArgument, "invalid public_key") } decodeString, err := hex.DecodeString(req.PublicKey[2:]) if err != nil { - return new(pb.CandidateResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, err.Error()) } pubkey := types.BytesToPubkey(decodeString) - cState, err := s.getStateForHeight(req.Height) + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.CandidateResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } if req.Height != 0 { cState.Lock() - cState.Candidates.LoadCandidates() - cState.Candidates.LoadStakesOfCandidate(pubkey) + cState.Candidates().LoadCandidates() + cState.Candidates().LoadStakesOfCandidate(pubkey) cState.Unlock() } cState.RLock() defer cState.RUnlock() - candidate := cState.Candidates.GetCandidate(pubkey) + candidate := cState.Candidates().GetCandidate(pubkey) if candidate == nil { - return new(pb.CandidateResponse), status.Error(codes.NotFound, "Candidate not found") + return nil, status.Error(codes.NotFound, "Candidate not found") } - result := makeResponseCandidate(cState, *candidate, true) + result := makeResponseCandidate(cState, candidate, true) return result, nil } -func makeResponseCandidate(state *state.State, c candidates.Candidate, includeStakes bool) *pb.CandidateResponse { +func makeResponseCandidate(state *state.CheckState, c *candidates.Candidate, includeStakes bool) *pb.CandidateResponse { candidate := &pb.CandidateResponse{ - RewardAddress: c.RewardAddress.String(), - TotalStake: state.Candidates.GetTotalStake(c.PubKey).String(), - PublicKey: c.PubKey.String(), - Commission: fmt.Sprintf("%d", c.Commission), - Status: fmt.Sprintf("%d", c.Status), + RewardAddress: c.RewardAddress.String(), + OwnerAddress: c.OwnerAddress.String(), + ControlAddress: c.ControlAddress.String(), + TotalStake: state.Candidates().GetTotalStake(c.PubKey).String(), + PublicKey: c.PubKey.String(), + Commission: uint64(c.Commission), + Status: uint64(c.Status), } if includeStakes { - stakes := state.Candidates.GetStakes(c.PubKey) - candidate.Stakes = make([]*pb.CandidateResponse_Stake, 0, len(stakes)) - for _, stake := range stakes { + addresses := map[types.Address]struct{}{} + minStake := big.NewInt(0) + stakes := state.Candidates().GetStakes(c.PubKey) + usedSlots := len(stakes) + candidate.UsedSlots = wrapperspb.UInt64(uint64(usedSlots)) + candidate.Stakes = make([]*pb.CandidateResponse_Stake, 0, usedSlots) + for i, stake := range stakes { candidate.Stakes = append(candidate.Stakes, &pb.CandidateResponse_Stake{ - Owner: stake.Owner.String(), - Coin: stake.Coin.String(), + Owner: stake.Owner.String(), + Coin: &pb.Coin{ + Id: uint64(stake.Coin), + Symbol: state.Coins().GetCoin(stake.Coin).GetFullSymbol(), + }, Value: stake.Value.String(), BipValue: stake.BipValue.String(), }) + addresses[stake.Owner] = struct{}{} + if usedSlots >= candidates.MaxDelegatorsPerCandidate { + if i != 0 && minStake.Cmp(stake.BipValue) != 1 { + continue + } + minStake = stake.BipValue + } } + candidate.UniqUsers = wrapperspb.UInt64(uint64(len(addresses))) + candidate.MinStake = wrapperspb.String(minStake.String()) } return candidate diff --git a/api/v2/service/candidates.go b/api/v2/service/candidates.go index 05a68526d..d07e9dec6 100644 --- a/api/v2/service/candidates.go +++ b/api/v2/service/candidates.go @@ -7,17 +7,18 @@ import ( "google.golang.org/grpc/status" ) -func (s *Service) Candidates(_ context.Context, req *pb.CandidatesRequest) (*pb.CandidatesResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// Candidates returns list of candidates. +func (s *Service) Candidates(ctx context.Context, req *pb.CandidatesRequest) (*pb.CandidatesResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.CandidatesResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } if req.Height != 0 { cState.Lock() - cState.Candidates.LoadCandidates() + cState.Candidates().LoadCandidates() if req.IncludeStakes { - cState.Candidates.LoadStakes() + cState.Candidates().LoadStakes() } cState.Unlock() } @@ -25,14 +26,21 @@ func (s *Service) Candidates(_ context.Context, req *pb.CandidatesRequest) (*pb. cState.RLock() defer cState.RUnlock() - candidates := cState.Candidates.GetCandidates() + candidates := cState.Candidates().GetCandidates() - result := &pb.CandidatesResponse{ - Candidates: make([]*pb.CandidateResponse, 0, len(candidates)), - } + response := &pb.CandidatesResponse{} for _, candidate := range candidates { - result.Candidates = append(result.Candidates, makeResponseCandidate(cState, *candidate, req.IncludeStakes)) + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + if req.Status != pb.CandidatesRequest_all && req.Status != pb.CandidatesRequest_CandidateStatus(candidate.Status) { + continue + } + + response.Candidates = append(response.Candidates, makeResponseCandidate(cState, candidate, req.IncludeStakes)) } - return result, nil + return response, nil } diff --git a/api/v2/service/coin_info.go b/api/v2/service/coin_info.go index 25ab45575..a4fd519cf 100644 --- a/api/v2/service/coin_info.go +++ b/api/v2/service/coin_info.go @@ -2,32 +2,87 @@ package service import ( "context" - "fmt" + "google.golang.org/protobuf/types/known/wrapperspb" + "strconv" + + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -func (s *Service) CoinInfo(_ context.Context, req *pb.CoinInfoRequest) (*pb.CoinInfoResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// CoinInfo returns information about coin symbol. +func (s *Service) CoinInfo(ctx context.Context, req *pb.CoinInfoRequest) (*pb.CoinInfoResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.CoinInfoResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } cState.RLock() defer cState.RUnlock() - coin := cState.Coins.GetCoin(types.StrToCoinSymbol(req.Symbol)) + coin := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.Symbol), types.GetVersionFromSymbol(req.Symbol)) if coin == nil { - return new(pb.CoinInfoResponse), status.Error(codes.FailedPrecondition, "Coin not found") + return nil, s.createError(status.New(codes.NotFound, "Coin not found"), transaction.EncodeError(code.NewCoinNotExists(req.Symbol, ""))) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + var ownerAddress *wrapperspb.StringValue + info := cState.Coins().GetSymbolInfo(coin.Symbol()) + if info != nil && info.OwnerAddress() != nil { + ownerAddress = wrapperspb.String(info.OwnerAddress().String()) + } + + return &pb.CoinInfoResponse{ + Id: uint64(coin.ID()), + Name: coin.Name(), + Symbol: coin.GetFullSymbol(), + Volume: coin.Volume().String(), + Crr: uint64(coin.Crr()), + ReserveBalance: coin.Reserve().String(), + MaxSupply: coin.MaxSupply().String(), + OwnerAddress: ownerAddress, + }, nil +} + +// CoinInfoById returns information about coin ID. +func (s *Service) CoinInfoById(ctx context.Context, req *pb.CoinIdRequest) (*pb.CoinInfoResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + cState.RLock() + defer cState.RUnlock() + + coin := cState.Coins().GetCoin(types.CoinID(req.Id)) + if coin == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin not found"), transaction.EncodeError(code.NewCoinNotExists("", strconv.Itoa(int(req.Id))))) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + var ownerAddress *wrapperspb.StringValue + info := cState.Coins().GetSymbolInfo(coin.Symbol()) + if info != nil && info.OwnerAddress() != nil { + ownerAddress = wrapperspb.String(info.OwnerAddress().String()) } return &pb.CoinInfoResponse{ + Id: uint64(coin.ID()), Name: coin.Name(), - Symbol: coin.Symbol().String(), + Symbol: coin.GetFullSymbol(), Volume: coin.Volume().String(), - Crr: fmt.Sprintf("%d", coin.Crr()), + Crr: uint64(coin.Crr()), ReserveBalance: coin.Reserve().String(), + MaxSupply: coin.MaxSupply().String(), + OwnerAddress: ownerAddress, }, nil } diff --git a/api/v2/service/data_encoder.go b/api/v2/service/data_encoder.go new file mode 100644 index 000000000..58410cc5f --- /dev/null +++ b/api/v2/service/data_encoder.go @@ -0,0 +1,228 @@ +package service + +import ( + "encoding/base64" + "encoding/json" + "errors" + "github.com/MinterTeam/minter-go-node/core/state/coins" + "github.com/MinterTeam/minter-go-node/core/transaction" + pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + "github.com/golang/protobuf/ptypes/any" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + _struct "google.golang.org/protobuf/types/known/structpb" + "strconv" +) + +func encode(data transaction.Data, coins coins.RCoins) (*any.Any, error) { + var m proto.Message + switch d := data.(type) { + case *transaction.BuyCoinData: + m = &pb.BuyCoinData{ + CoinToBuy: &pb.Coin{ + Id: uint64(d.CoinToBuy), + Symbol: coins.GetCoin(d.CoinToBuy).GetFullSymbol(), + }, + ValueToBuy: d.ValueToBuy.String(), + CoinToSell: &pb.Coin{ + Id: uint64(d.CoinToSell), + Symbol: coins.GetCoin(d.CoinToSell).GetFullSymbol(), + }, + MaximumValueToSell: d.MaximumValueToSell.String(), + } + case *transaction.EditCoinOwnerData: + m = &pb.EditCoinOwnerData{ + Symbol: d.Symbol.String(), + NewOwner: d.NewOwner.String(), + } + case *transaction.CreateCoinData: + m = &pb.CreateCoinData{ + Name: d.Name, + Symbol: d.Symbol.String(), + InitialAmount: d.InitialAmount.String(), + InitialReserve: d.InitialReserve.String(), + ConstantReserveRatio: uint64(d.ConstantReserveRatio), + MaxSupply: d.MaxSupply.String(), + } + case *transaction.CreateMultisigData: + weights := make([]uint64, 0, len(d.Weights)) + for _, weight := range d.Weights { + weights = append(weights, uint64(weight)) + } + addresses := make([]string, 0, len(d.Addresses)) + for _, address := range d.Addresses { + addresses = append(addresses, address.String()) + } + m = &pb.CreateMultisigData{ + Threshold: uint64(d.Threshold), + Weights: weights, + Addresses: addresses, + } + case *transaction.DeclareCandidacyData: + m = &pb.DeclareCandidacyData{ + Address: d.Address.String(), + PubKey: d.PubKey.String(), + Commission: uint64(d.Commission), + Coin: &pb.Coin{ + Id: uint64(d.Coin), + Symbol: coins.GetCoin(d.Coin).GetFullSymbol(), + }, + Stake: d.Stake.String(), + } + case *transaction.DelegateData: + m = &pb.DelegateData{ + PubKey: d.PubKey.String(), + Coin: &pb.Coin{ + Id: uint64(d.Coin), + Symbol: coins.GetCoin(d.Coin).GetFullSymbol(), + }, + Value: d.Value.String(), + } + case *transaction.EditCandidateData: + m = &pb.EditCandidateData{ + PubKey: d.PubKey.String(), + RewardAddress: d.RewardAddress.String(), + OwnerAddress: d.OwnerAddress.String(), + ControlAddress: d.ControlAddress.String(), + } + case *transaction.EditCandidatePublicKeyData: + m = &pb.EditCandidatePublicKeyData{ + PubKey: d.PubKey.String(), + NewPubKey: d.NewPubKey.String(), + } + case *transaction.EditMultisigData: + weights := make([]uint64, 0, len(d.Weights)) + for _, weight := range d.Weights { + weights = append(weights, uint64(weight)) + } + addresses := make([]string, 0, len(d.Addresses)) + for _, address := range d.Addresses { + addresses = append(addresses, address.String()) + } + m = &pb.EditMultisigData{ + Threshold: uint64(d.Threshold), + Weights: weights, + Addresses: addresses, + } + case *transaction.MultisendData: + list := make([]*pb.SendData, 0, len(d.List)) + for _, item := range d.List { + list = append(list, &pb.SendData{ + Coin: &pb.Coin{ + Id: uint64(item.Coin), + Symbol: coins.GetCoin(item.Coin).GetFullSymbol(), + }, + To: item.To.String(), + Value: item.Value.String(), + }) + } + m = &pb.MultiSendData{ + List: list, + } + case *transaction.PriceVoteData: + m = &pb.PriceVoteData{ + Price: strconv.Itoa(int(d.Price)), + } + case *transaction.RecreateCoinData: + m = &pb.RecreateCoinData{ + Name: d.Name, + Symbol: d.Symbol.String(), + InitialAmount: d.InitialAmount.String(), + InitialReserve: d.InitialReserve.String(), + ConstantReserveRatio: uint64(d.ConstantReserveRatio), + MaxSupply: d.MaxSupply.String(), + } + case *transaction.RedeemCheckData: + m = &pb.RedeemCheckData{ + RawCheck: base64.StdEncoding.EncodeToString(d.RawCheck), + Proof: base64.StdEncoding.EncodeToString(d.Proof[:]), + } + case *transaction.SellAllCoinData: + m = &pb.SellAllCoinData{ + CoinToSell: &pb.Coin{ + Id: uint64(d.CoinToSell), + Symbol: coins.GetCoin(d.CoinToSell).GetFullSymbol(), + }, + CoinToBuy: &pb.Coin{ + Id: uint64(d.CoinToBuy), + Symbol: coins.GetCoin(d.CoinToBuy).GetFullSymbol(), + }, + MinimumValueToBuy: d.MinimumValueToBuy.String(), + } + case *transaction.SellCoinData: + m = &pb.SellCoinData{ + CoinToSell: &pb.Coin{ + Id: uint64(d.CoinToSell), + Symbol: coins.GetCoin(d.CoinToSell).GetFullSymbol(), + }, + ValueToSell: d.ValueToSell.String(), + CoinToBuy: &pb.Coin{ + Id: uint64(d.CoinToBuy), + Symbol: coins.GetCoin(d.CoinToBuy).GetFullSymbol(), + }, + MinimumValueToBuy: d.MinimumValueToBuy.String(), + } + case *transaction.SendData: + m = &pb.SendData{ + Coin: &pb.Coin{ + Id: uint64(d.Coin), + Symbol: coins.GetCoin(d.Coin).GetFullSymbol(), + }, + To: d.To.String(), + Value: d.Value.String(), + } + case *transaction.SetHaltBlockData: + m = &pb.SetHaltBlockData{ + PubKey: d.PubKey.String(), + Height: uint64(d.Height), + } + case *transaction.SetCandidateOnData: + m = &pb.SetCandidateOnData{ + PubKey: d.PubKey.String(), + } + case *transaction.SetCandidateOffData: + m = &pb.SetCandidateOffData{ + PubKey: d.PubKey.String(), + } + case *transaction.UnbondData: + m = &pb.UnbondData{ + PubKey: d.PubKey.String(), + Coin: &pb.Coin{ + Id: uint64(d.Coin), + Symbol: coins.GetCoin(d.Coin).GetFullSymbol(), + }, + Value: d.Value.String(), + } + default: + return nil, errors.New("unknown tx type") + } + + a, err := anypb.New(m) + if err != nil { + return nil, err + } + + return a, nil +} + +func encodeToStruct(b []byte) (*_struct.Struct, error) { + dataStruct := &_struct.Struct{} + if err := dataStruct.UnmarshalJSON(b); err != nil { + return nil, err + } + + return dataStruct, nil +} + +func toStruct(d interface{}) (*_struct.Struct, error) { + byteData, err := json.Marshal(d) + if err != nil { + return nil, err + } + + data, err := encodeToStruct(byteData) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/api/v2/service/estimate_coin_buy.go b/api/v2/service/estimate_coin_buy.go index 8d2e45ec1..f9f8f9d84 100644 --- a/api/v2/service/estimate_coin_buy.go +++ b/api/v2/service/estimate_coin_buy.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" @@ -13,90 +14,95 @@ import ( "math/big" ) -func (s *Service) EstimateCoinBuy(_ context.Context, req *pb.EstimateCoinBuyRequest) (*pb.EstimateCoinBuyResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// EstimateCoinBuy return estimate of buy coin transaction. +func (s *Service) EstimateCoinBuy(ctx context.Context, req *pb.EstimateCoinBuyRequest) (*pb.EstimateCoinBuyResponse, error) { + valueToBuy, ok := big.NewInt(0).SetString(req.ValueToBuy, 10) + if !ok { + return nil, status.Error(codes.InvalidArgument, "Value to buy not specified") + } + + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.EstimateCoinBuyResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } cState.RLock() defer cState.RUnlock() - coinToSell := types.StrToCoinSymbol(req.CoinToSell) - coinToBuy := types.StrToCoinSymbol(req.CoinToBuy) - - var result *big.Int - - if coinToSell == coinToBuy { - return new(pb.EstimateCoinBuyResponse), status.Error(codes.FailedPrecondition, "\"From\" coin equals to \"to\" coin") + var coinToBuy types.CoinID + if req.GetCoinToBuy() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToBuy()), types.GetVersionFromSymbol(req.GetCoinToBuy())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToBuy(), ""))) + } + coinToBuy = symbol.ID() + } else { + coinToBuy = types.CoinID(req.GetCoinIdToBuy()) + if !cState.Coins().Exists(coinToBuy) { + return nil, s.createError(status.New(codes.NotFound, "Coin to buy not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToBuy.String()))) + } } - if !cState.Coins.Exists(coinToSell) { - return new(pb.EstimateCoinBuyResponse), status.Error(codes.FailedPrecondition, "Coin to sell not exists") + var coinToSell types.CoinID + if req.GetCoinToSell() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToSell()), types.GetVersionFromSymbol(req.GetCoinToSell())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToSell(), ""))) + } + coinToSell = symbol.ID() + } else { + coinToSell = types.CoinID(req.GetCoinIdToSell()) + if !cState.Coins().Exists(coinToSell) { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToSell.String()))) + } } - if !cState.Coins.Exists(coinToBuy) { - return new(pb.EstimateCoinBuyResponse), status.Error(codes.FailedPrecondition, "Coin to buy not exists") + if coinToSell == coinToBuy { + return nil, s.createError(status.New(codes.InvalidArgument, "\"From\" coin equals to \"to\" coin"), + transaction.EncodeError(code.NewCrossConvert(coinToSell.String(), cState.Coins().GetCoin(coinToSell).GetFullSymbol(), coinToBuy.String(), cState.Coins().GetCoin(coinToBuy).GetFullSymbol()))) } - commissionInBaseCoin := big.NewInt(commissions.ConvertTx) - commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) + commissionInBaseCoin := big.NewInt(0).Mul(big.NewInt(commissions.ConvertTx), transaction.CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - if coinToSell != types.GetBaseCoin() { - coin := cState.Coins.GetCoin(coinToSell) - - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return new(pb.EstimateCoinBuyResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), commissionInBaseCoin.String())), transaction.EncodeError(map[string]string{ - "has": coin.Reserve().String(), - "required": commissionInBaseCoin.String(), - })) + coinFrom := cState.Coins().GetCoin(coinToSell) + coinTo := cState.Coins().GetCoin(coinToBuy) + + if !coinToSell.IsBaseCoin() { + if coinFrom.Reserve().Cmp(commissionInBaseCoin) < 0 { + return nil, s.createError( + status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", + coinFrom.Reserve().String(), commissionInBaseCoin.String())), + transaction.EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), + ) } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) - } - - valueToBuy, ok := big.NewInt(0).SetString(req.ValueToBuy, 10) - if !ok { - return new(pb.EstimateCoinBuyResponse), status.Error(codes.InvalidArgument, "Value to buy not specified") + commission = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), commissionInBaseCoin) } - switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) - result = formula.CalculatePurchaseAmount(coin.Volume(), coin.Reserve(), coin.Crr(), valueToBuy) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) + value := valueToBuy - if coin.Reserve().Cmp(valueToBuy) < 0 { - return new(pb.EstimateCoinBuyResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), valueToBuy.String())), transaction.EncodeError(map[string]string{ - "has": coin.Reserve().String(), - "required": valueToBuy.String(), - })) + if !coinToBuy.IsBaseCoin() { + if errResp := transaction.CheckForCoinSupplyOverflow(coinTo, valueToBuy); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) } + value = formula.CalculatePurchaseAmount(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToBuy) + } - result = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), valueToBuy) - default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) - baseCoinNeeded := formula.CalculatePurchaseAmount(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToBuy) - - if coinFrom.Reserve().Cmp(baseCoinNeeded) < 0 { - return new(pb.EstimateCoinBuyResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coinFrom.Reserve().String(), baseCoinNeeded.String())), transaction.EncodeError(map[string]string{ - "has": coinFrom.Reserve().String(), - "required": baseCoinNeeded.String(), - })) - + if !coinToSell.IsBaseCoin() { + value = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), value) + if errResp := transaction.CheckReserveUnderflow(coinFrom, value); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) } - - result = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), baseCoinNeeded) } return &pb.EstimateCoinBuyResponse{ - WillPay: result.String(), + WillPay: value.String(), Commission: commission.String(), }, nil } diff --git a/api/v2/service/estimate_coin_sell.go b/api/v2/service/estimate_coin_sell.go index 6b9f290f7..b63708ad5 100644 --- a/api/v2/service/estimate_coin_sell.go +++ b/api/v2/service/estimate_coin_sell.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" @@ -13,85 +14,96 @@ import ( "math/big" ) -func (s *Service) EstimateCoinSell(_ context.Context, req *pb.EstimateCoinSellRequest) (*pb.EstimateCoinSellResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// EstimateCoinSell return estimate of sell coin transaction. +func (s *Service) EstimateCoinSell(ctx context.Context, req *pb.EstimateCoinSellRequest) (*pb.EstimateCoinSellResponse, error) { + valueToSell, ok := big.NewInt(0).SetString(req.ValueToSell, 10) + if !ok { + return nil, status.Error(codes.InvalidArgument, "Value to sell not specified") + } + + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.EstimateCoinSellResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } cState.RLock() defer cState.RUnlock() - coinToSell := types.StrToCoinSymbol(req.CoinToSell) - coinToBuy := types.StrToCoinSymbol(req.CoinToBuy) - - if coinToSell == coinToBuy { - return new(pb.EstimateCoinSellResponse), s.createError(status.New(codes.InvalidArgument, "\"From\" coin equals to \"to\" coin"), transaction.EncodeError(map[string]string{ - "coin_to_sell": coinToSell.String(), - "coin_to_buy": coinToBuy.String(), - })) + var coinToBuy types.CoinID + if req.GetCoinToBuy() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToBuy()), types.GetVersionFromSymbol(req.GetCoinToBuy())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to buy not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToBuy(), ""))) + } + coinToBuy = symbol.ID() + } else { + coinToBuy = types.CoinID(req.GetCoinIdToBuy()) + if !cState.Coins().Exists(coinToBuy) { + return nil, s.createError(status.New(codes.NotFound, "Coin to buy not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToBuy.String()))) + } } - if !cState.Coins.Exists(coinToSell) { - return new(pb.EstimateCoinSellResponse), s.createError(status.New(codes.InvalidArgument, "Coin to sell not exists"), transaction.EncodeError(map[string]string{ - "coin_to_sell": coinToSell.String(), - })) + var coinToSell types.CoinID + if req.GetCoinToSell() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToSell()), types.GetVersionFromSymbol(req.GetCoinToSell())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToSell(), ""))) + } + coinToSell = symbol.ID() + } else { + coinToSell = types.CoinID(req.GetCoinIdToSell()) + if !cState.Coins().Exists(coinToSell) { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToSell.String()))) + } } - if !cState.Coins.Exists(coinToBuy) { - return new(pb.EstimateCoinSellResponse), s.createError(status.New(codes.InvalidArgument, "Coin to buy not exists"), transaction.EncodeError(map[string]string{ - "coin_to_buy": coinToSell.String(), - })) - + if coinToSell == coinToBuy { + return nil, s.createError(status.New(codes.InvalidArgument, "\"From\" coin equals to \"to\" coin"), + transaction.EncodeError(code.NewCrossConvert(coinToSell.String(), cState.Coins().GetCoin(coinToSell).GetFullSymbol(), coinToBuy.String(), cState.Coins().GetCoin(coinToBuy).GetFullSymbol()))) } - commissionInBaseCoin := big.NewInt(commissions.ConvertTx) - commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) + commissionInBaseCoin := big.NewInt(0).Mul(big.NewInt(commissions.ConvertTx), transaction.CommissionMultiplier) commission := big.NewInt(0).Set(commissionInBaseCoin) - valueToSell, ok := big.NewInt(0).SetString(req.ValueToSell, 10) - if !ok { - return new(pb.EstimateCoinSellResponse), status.Error(codes.InvalidArgument, "Value to sell not specified") - } - if coinToSell != types.GetBaseCoin() { - coin := cState.Coins.GetCoin(coinToSell) + coinFrom := cState.Coins().GetCoin(coinToSell) + coinTo := cState.Coins().GetCoin(coinToBuy) - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return new(pb.EstimateCoinSellResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), commissionInBaseCoin.String())), transaction.EncodeError(map[string]string{ - "has": coin.Reserve().String(), - "required": commissionInBaseCoin.String(), - })) + if !coinToSell.IsBaseCoin() { + if coinFrom.Reserve().Cmp(commissionInBaseCoin) < 0 { + return nil, s.createError( + status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", + coinFrom.Reserve().String(), commissionInBaseCoin.String())), + transaction.EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), + ) } - if coin.Volume().Cmp(valueToSell) < 0 { - return new(pb.EstimateCoinSellResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), valueToSell.String())), transaction.EncodeError(map[string]string{ - "has": coin.Reserve().String(), - "required": valueToSell.String(), - })) - } + commission = formula.CalculateSaleAmount(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), commissionInBaseCoin) + } + + value := valueToSell - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + if !coinToSell.IsBaseCoin() { + value = formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) + if errResp := transaction.CheckReserveUnderflow(coinFrom, value); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) + } } - var result = big.NewInt(0) - switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) - result.Set(formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell)) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) - result.Set(formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell)) - default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) - basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) - result.Set(formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), basecoinValue)) + if !coinToBuy.IsBaseCoin() { + if errResp := transaction.CheckForCoinSupplyOverflow(coinTo, value); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) + } + value = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), value) } - return &pb.EstimateCoinSellResponse{ - WillGet: result.String(), + res := &pb.EstimateCoinSellResponse{ + WillGet: value.String(), Commission: commission.String(), - }, nil + } + return res, nil } diff --git a/api/v2/service/estimate_coin_sell_all.go b/api/v2/service/estimate_coin_sell_all.go index a30895acf..681be2443 100644 --- a/api/v2/service/estimate_coin_sell_all.go +++ b/api/v2/service/estimate_coin_sell_all.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" @@ -13,102 +14,98 @@ import ( "math/big" ) -func (s *Service) EstimateCoinSellAll(_ context.Context, req *pb.EstimateCoinSellAllRequest) (*pb.EstimateCoinSellAllResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// EstimateCoinSellAll return estimate of sell all coin transaction. +func (s *Service) EstimateCoinSellAll(ctx context.Context, req *pb.EstimateCoinSellAllRequest) (*pb.EstimateCoinSellAllResponse, error) { + valueToSell, ok := big.NewInt(0).SetString(req.ValueToSell, 10) + if !ok { + return nil, status.Error(codes.InvalidArgument, "Value to sell not specified") + } + + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.EstimateCoinSellAllResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } cState.RLock() defer cState.RUnlock() - gasPrice := req.GasPrice - if gasPrice < 1 { - gasPrice = 1 + var coinToBuy types.CoinID + if req.GetCoinToBuy() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToBuy()), types.GetVersionFromSymbol(req.GetCoinToBuy())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to buy not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToBuy(), ""))) + } + coinToBuy = symbol.ID() + } else { + coinToBuy = types.CoinID(req.GetCoinIdToBuy()) + if !cState.Coins().Exists(coinToBuy) { + return nil, s.createError(status.New(codes.NotFound, "Coin to buy not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToBuy.String()))) + } } - coinToSell := types.StrToCoinSymbol(req.CoinToSell) - coinToBuy := types.StrToCoinSymbol(req.CoinToBuy) - valueToSell, ok := big.NewInt(0).SetString(req.ValueToSell, 10) - if !ok { - return new(pb.EstimateCoinSellAllResponse), status.Error(codes.InvalidArgument, "Value to sell not specified") + var coinToSell types.CoinID + if req.GetCoinToSell() != "" { + symbol := cState.Coins().GetCoinBySymbol(types.StrToCoinBaseSymbol(req.GetCoinToSell()), types.GetVersionFromSymbol(req.GetCoinToSell())) + if symbol == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists(req.GetCoinToSell(), ""))) + } + coinToSell = symbol.ID() + } else { + coinToSell = types.CoinID(req.GetCoinIdToSell()) + if !cState.Coins().Exists(coinToSell) { + return nil, + s.createError(status.New(codes.NotFound, "Coin to sell not exists"), transaction.EncodeError(code.NewCoinNotExists("", coinToSell.String()))) + } } - var result *big.Int - if coinToSell == coinToBuy { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.InvalidArgument, "\"From\" coin equals to \"to\" coin"), transaction.EncodeError(map[string]string{ - "coin_to_sell": coinToSell.String(), - "coin_to_buy": coinToBuy.String(), - })) - } - - if !cState.Coins.Exists(coinToSell) { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.InvalidArgument, "Coin to sell not exists"), transaction.EncodeError(map[string]string{ - "coin_to_sell": coinToSell.String(), - })) - } - - if !cState.Coins.Exists(coinToBuy) { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.InvalidArgument, "Coin to buy not exists"), transaction.EncodeError(map[string]string{ - "coin_to_buy": coinToBuy.String(), - })) + return nil, s.createError(status.New(codes.InvalidArgument, "\"From\" coin equals to \"to\" coin"), + transaction.EncodeError(code.NewCrossConvert(coinToSell.String(), cState.Coins().GetCoin(coinToSell).GetFullSymbol(), coinToBuy.String(), cState.Coins().GetCoin(coinToBuy).GetFullSymbol()))) } commissionInBaseCoin := big.NewInt(commissions.ConvertTx) - commissionInBaseCoin.Mul(commissionInBaseCoin, transaction.CommissionMultiplier) - commission := big.NewInt(0).Set(commissionInBaseCoin) - - switch { - case coinToSell == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToBuy) - - valueToSell.Sub(valueToSell, commission) - if valueToSell.Cmp(big.NewInt(0)) != 1 { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.InvalidArgument, "Not enough coins to pay commission"), transaction.EncodeError(map[string]string{ - "value_to_sell": valueToSell.String(), - "coin_to_sell": coinToSell.String(), - "commission": commission.String(), - })) + if req.GasPrice > 1 { + commissionInBaseCoin.Mul(commissionInBaseCoin, big.NewInt(int64(req.GasPrice))) + } + commissionInBaseCoin = big.NewInt(0).Mul(commissionInBaseCoin, transaction.CommissionMultiplier) + + coinFrom := cState.Coins().GetCoin(coinToSell) + coinTo := cState.Coins().GetCoin(coinToBuy) + + value := valueToSell + + if !coinToSell.IsBaseCoin() { + if coinFrom.Reserve().Cmp(commissionInBaseCoin) < 0 { + return nil, s.createError( + status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", + coinFrom.Reserve().String(), commissionInBaseCoin.String())), + transaction.EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), + ) } - result = formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) - case coinToBuy == types.GetBaseCoin(): - coin := cState.Coins.GetCoin(coinToSell) - result = formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), valueToSell) - - result.Sub(result, commission) - if result.Cmp(big.NewInt(0)) != 1 { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.InvalidArgument, "Not enough coins to pay commission"), transaction.EncodeError(map[string]string{ - "value_to_sell": valueToSell.String(), - "coin_to_sell": coinToSell.String(), - "coin_reserve_to_sell": coin.Reserve().String(), - "coin_crr_to_sell": fmt.Sprintf("%d", coin.Crr()), - "result": result.String(), - "commission": commission.String(), - })) + value = formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) + if errResp := transaction.CheckReserveUnderflow(coinFrom, value); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) } - default: - coinFrom := cState.Coins.GetCoin(coinToSell) - coinTo := cState.Coins.GetCoin(coinToBuy) - basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), valueToSell) + } - basecoinValue.Sub(basecoinValue, commission) - if basecoinValue.Cmp(big.NewInt(0)) != 1 { - return new(pb.EstimateCoinSellAllResponse), s.createError(status.New(codes.FailedPrecondition, "Not enough coins to pay commission"), transaction.EncodeError(map[string]string{ - "coin_to_sell": coinToSell.String(), - "coin_to_buy": coinToBuy.String(), - "coin_to_sell_crr": fmt.Sprintf("%d", coinFrom.Crr()), - "coin_to_sell_reserve": coinFrom.Reserve().String(), - "result": basecoinValue.String(), - "commission": commission.String(), - })) + if !coinToBuy.IsBaseCoin() { + if errResp := transaction.CheckForCoinSupplyOverflow(coinTo, value); errResp != nil { + return nil, s.createError(status.New(codes.FailedPrecondition, errResp.Log), errResp.Info) } - - result = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), basecoinValue) + value.Sub(value, valueToSell) + if value.Sign() != 1 { + return nil, status.New(codes.FailedPrecondition, "Not enough coins to pay commission").Err() + } + value = formula.CalculatePurchaseReturn(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), value) } return &pb.EstimateCoinSellAllResponse{ - WillGet: result.String(), + WillGet: value.String(), }, nil } diff --git a/api/v2/service/estimate_tx_commission.go b/api/v2/service/estimate_tx_commission.go index b1d78040d..50820296c 100644 --- a/api/v2/service/estimate_tx_commission.go +++ b/api/v2/service/estimate_tx_commission.go @@ -4,50 +4,58 @@ import ( "context" "encoding/hex" "fmt" + "math/big" + "strings" + + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/formula" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "math/big" ) -func (s *Service) EstimateTxCommission(_ context.Context, req *pb.EstimateTxCommissionRequest) (*pb.EstimateTxCommissionResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// EstimateTxCommission return estimate of transaction. +func (s *Service) EstimateTxCommission(ctx context.Context, req *pb.EstimateTxCommissionRequest) (*pb.EstimateTxCommissionResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.EstimateTxCommissionResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } cState.RLock() defer cState.RUnlock() - if len(req.Tx) < 3 { - return new(pb.EstimateTxCommissionResponse), status.Error(codes.InvalidArgument, "invalid tx") + if !strings.HasPrefix(strings.Title(req.GetTx()), "0x") { + return nil, status.Error(codes.InvalidArgument, "invalid transaction") } - decodeString, err := hex.DecodeString(req.Tx[2:]) + decodeString, err := hex.DecodeString(req.GetTx()[2:]) if err != nil { - return new(pb.EstimateTxCommissionResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, err.Error()) } decodedTx, err := transaction.TxDecoder.DecodeFromBytesWithoutSig(decodeString) if err != nil { - return new(pb.EstimateTxCommissionResponse), status.Error(codes.InvalidArgument, "Cannot decode transaction") + return nil, status.Errorf(codes.InvalidArgument, "Cannot decode transaction: %s", err.Error()) } commissionInBaseCoin := decodedTx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) if !decodedTx.GasCoin.IsBaseCoin() { - coin := cState.Coins.GetCoin(decodedTx.GasCoin) + coin := cState.Coins().GetCoin(decodedTx.GasCoin) if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return new(pb.EstimateTxCommissionResponse), s.createError(status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), commissionInBaseCoin.String())), transaction.EncodeError(map[string]string{ - "commission_in_base_coin": coin.Reserve().String(), - "value_has": coin.Reserve().String(), - "value_required": commissionInBaseCoin.String(), - })) + return nil, s.createError( + status.New(codes.InvalidArgument, fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", + coin.Reserve().String(), commissionInBaseCoin.String())), + transaction.EncodeError(code.NewCoinReserveNotSufficient( + coin.GetFullSymbol(), + coin.ID().String(), + coin.Reserve().String(), + commissionInBaseCoin.String(), + )), + ) } commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) diff --git a/api/v2/service/events.go b/api/v2/service/events.go index eccce3bf2..6eb42c553 100644 --- a/api/v2/service/events.go +++ b/api/v2/service/events.go @@ -1,44 +1,52 @@ package service import ( - "bytes" "context" - "encoding/json" - compact_db "github.com/MinterTeam/events-db" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" - "github.com/golang/protobuf/jsonpb" _struct "github.com/golang/protobuf/ptypes/struct" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) -func (s *Service) Events(_ context.Context, req *pb.EventsRequest) (*pb.EventsResponse, error) { - events := s.blockchain.GetEventsDB().LoadEvents(req.Height) - resultEvents := make([]*pb.EventsResponse_Event, 0, len(events)) +// Events returns events at given height. +func (s *Service) Events(ctx context.Context, req *pb.EventsRequest) (*pb.EventsResponse, error) { + currentHeight := s.blockchain.Height() + if req.Height > currentHeight { + return nil, status.Errorf(codes.NotFound, "wanted to load target %d but only found up to %d", req.Height, currentHeight) + } + + height := uint32(req.Height) + events := s.blockchain.GetEventsDB().LoadEvents(height) + resultEvents := make([]*_struct.Struct, 0, len(events)) for _, event := range events { - byteData, err := json.Marshal(event) - if err != nil { - return nil, err + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } - var bb bytes.Buffer - bb.Write(byteData) - data := &_struct.Struct{Fields: make(map[string]*_struct.Value)} - if err := (&jsonpb.Unmarshaler{}).Unmarshal(&bb, data); err != nil { - return nil, err + var find = true + for _, s := range req.Search { + if event.AddressString() == s || event.ValidatorPubKeyString() == s { + find = true + break + } + find = false + } + if !find { + continue } - var t string - switch event.(type) { - case *compact_db.RewardEvent: - t = "minter/RewardEvent" - case *compact_db.SlashEvent: - t = "minter/SlashEvent" - case *compact_db.UnbondEvent: - t = "minter/UnbondEvent" - default: - t = "Undefined Type" + marshalJSON, err := s.cdc.MarshalJSON(event) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + + data, err := encodeToStruct(marshalJSON) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) } - resultEvents = append(resultEvents, &pb.EventsResponse_Event{Type: t, Value: data}) + resultEvents = append(resultEvents, data) } return &pb.EventsResponse{ Events: resultEvents, diff --git a/api/v2/service/frozen.go b/api/v2/service/frozen.go new file mode 100644 index 000000000..34eb21ce6 --- /dev/null +++ b/api/v2/service/frozen.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state/candidates" + "github.com/MinterTeam/minter-go-node/core/state/coins" + "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" + pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "strings" +) + +// Frozen returns frozen balance. +func (s *Service) Frozen(ctx context.Context, req *pb.FrozenRequest) (*pb.FrozenResponse, error) { + if !strings.HasPrefix(strings.Title(req.Address), "Mx") { + return nil, status.Error(codes.InvalidArgument, "invalid address") + } + + cState := s.blockchain.CurrentState() + cState.RLock() + defer cState.RUnlock() + + var reqCoin *coins.Model + + if req.CoinId != nil { + coinID := types.CoinID(req.CoinId.GetValue()) + reqCoin = cState.Coins().GetCoin(coinID) + if reqCoin == nil { + return nil, s.createError(status.New(codes.NotFound, "Coin not found"), transaction.EncodeError(code.NewCoinNotExists("", coinID.String()))) + } + } + var frozen []*pb.FrozenResponse_Frozen + + cState.FrozenFunds().GetFrozenFunds(s.blockchain.Height()) + + for i := s.blockchain.Height(); i <= s.blockchain.Height()+candidates.UnbondPeriod; i++ { + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + funds := cState.FrozenFunds().GetFrozenFunds(i) + if funds == nil { + continue + } + + for _, fund := range funds.List { + if fund.Address.String() != req.Address { + continue + } + coin := reqCoin + if coin == nil { + coin = cState.Coins().GetCoin(fund.Coin) + } else { + if coin.ID() != fund.Coin { + continue + } + } + frozen = append(frozen, &pb.FrozenResponse_Frozen{ + Height: funds.Height(), + Address: fund.Address.String(), + CandidateKey: fund.CandidateKey.String(), + Coin: &pb.Coin{ + Id: uint64(fund.Coin), + Symbol: coin.GetFullSymbol(), + }, + Value: fund.Value.String(), + }) + } + } + + return &pb.FrozenResponse{Frozen: frozen}, nil +} diff --git a/api/v2/service/gas.go b/api/v2/service/gas.go index a64c575fc..81f77a8e7 100644 --- a/api/v2/service/gas.go +++ b/api/v2/service/gas.go @@ -2,26 +2,27 @@ package service import ( "context" - "fmt" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "github.com/golang/protobuf/ptypes/empty" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +// MinGasPrice returns current min gas price. func (s *Service) MinGasPrice(context.Context, *empty.Empty) (*pb.MinGasPriceResponse, error) { return &pb.MinGasPriceResponse{ - MinGasPrice: fmt.Sprintf("%d", s.blockchain.MinGasPrice()), + MinGasPrice: uint64(s.blockchain.MinGasPrice()), }, nil } -func (s *Service) MaxGas(_ context.Context, req *pb.MaxGasRequest) (*pb.MaxGasResponse, error) { - cState, err := s.getStateForHeight(req.Height) +// MaxGas returns current max gas. +func (s *Service) MaxGasPrice(ctx context.Context, req *pb.MaxGasPriceRequest) (*pb.MaxGasPriceResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.MaxGasResponse), status.Error(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } - return &pb.MaxGasResponse{ - MaxGas: fmt.Sprintf("%d", cState.App.GetMaxGas()), + return &pb.MaxGasPriceResponse{ + MaxGasPrice: cState.App().GetMaxGas(), }, nil } diff --git a/api/v2/service/genesis.go b/api/v2/service/genesis.go index d2184c6df..91e17316d 100644 --- a/api/v2/service/genesis.go +++ b/api/v2/service/genesis.go @@ -1,31 +1,34 @@ package service import ( - "bytes" "context" - "fmt" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" - "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/ptypes/empty" - _struct "github.com/golang/protobuf/ptypes/struct" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" "time" ) -func (s *Service) Genesis(context.Context, *empty.Empty) (*pb.GenesisResponse, error) { +// Genesis returns genesis file. +func (s *Service) Genesis(ctx context.Context, _ *empty.Empty) (*pb.GenesisResponse, error) { result, err := s.client.Genesis() if err != nil { - return new(pb.GenesisResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, status.Error(codes.FailedPrecondition, err.Error()) } - var bb bytes.Buffer - if _, err := bb.Write(result.Genesis.AppState); err != nil { - return new(pb.GenesisResponse), status.Error(codes.Internal, err.Error()) + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } - appState := &_struct.Struct{Fields: make(map[string]*_struct.Value)} - if err := (&jsonpb.Unmarshaler{}).Unmarshal(&bb, appState); err != nil { - return new(pb.GenesisResponse), status.Error(codes.Internal, err.Error()) + + var appState pb.GenesisResponse_AppState + err = protojson.Unmarshal(result.Genesis.AppState, &appState) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } return &pb.GenesisResponse{ @@ -33,19 +36,19 @@ func (s *Service) Genesis(context.Context, *empty.Empty) (*pb.GenesisResponse, e ChainId: result.Genesis.ChainID, ConsensusParams: &pb.GenesisResponse_ConsensusParams{ Block: &pb.GenesisResponse_ConsensusParams_Block{ - MaxBytes: fmt.Sprintf("%d", result.Genesis.ConsensusParams.Block.MaxBytes), - MaxGas: fmt.Sprintf("%d", result.Genesis.ConsensusParams.Block.MaxGas), - TimeIotaMs: fmt.Sprintf("%d", result.Genesis.ConsensusParams.Block.TimeIotaMs), + MaxBytes: result.Genesis.ConsensusParams.Block.MaxBytes, + MaxGas: result.Genesis.ConsensusParams.Block.MaxGas, + TimeIotaMs: result.Genesis.ConsensusParams.Block.TimeIotaMs, }, Evidence: &pb.GenesisResponse_ConsensusParams_Evidence{ - MaxAgeNumBlocks: fmt.Sprintf("%d", result.Genesis.ConsensusParams.Evidence.MaxAgeNumBlocks), - MaxAgeDuration: fmt.Sprintf("%d", result.Genesis.ConsensusParams.Evidence.MaxAgeDuration), + MaxAgeNumBlocks: result.Genesis.ConsensusParams.Evidence.MaxAgeNumBlocks, + MaxAgeDuration: int64(result.Genesis.ConsensusParams.Evidence.MaxAgeDuration), }, Validator: &pb.GenesisResponse_ConsensusParams_Validator{ - PublicKeyTypes: result.Genesis.ConsensusParams.Validator.PubKeyTypes, + PubKeyTypes: result.Genesis.ConsensusParams.Validator.PubKeyTypes, }, }, AppHash: result.Genesis.AppHash.String(), - AppState: appState, + AppState: &appState, }, nil } diff --git a/api/v2/service/halts.go b/api/v2/service/halts.go new file mode 100644 index 000000000..2f3280779 --- /dev/null +++ b/api/v2/service/halts.go @@ -0,0 +1,31 @@ +package service + +import ( + "context" + pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// Halts returns votes +func (s *Service) Halts(_ context.Context, req *pb.HaltsRequest) (*pb.HaltsResponse, error) { + cState, err := s.blockchain.GetStateForHeight(req.Height) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + blocks := cState.Halts().GetHaltBlocks(req.Height) + + if blocks == nil { + return &pb.HaltsResponse{}, nil + } + + var votes []string + for _, vote := range blocks.List { + votes = append(votes, vote.Pubkey.String()) + } + + return &pb.HaltsResponse{ + PublicKeys: votes, + }, nil +} diff --git a/api/v2/service/missed_blocks.go b/api/v2/service/missed_blocks.go index 835780e0f..776d70d57 100644 --- a/api/v2/service/missed_blocks.go +++ b/api/v2/service/missed_blocks.go @@ -2,15 +2,40 @@ package service import ( "context" - "fmt" + "github.com/MinterTeam/minter-go-node/core/types" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "strings" ) -func (s *Service) MissedBlocks(_ context.Context, req *pb.MissedBlocksRequest) (*pb.MissedBlocksResponse, error) { - blocks, count, err := s.blockchain.MissedBlocks(req.PublicKey, uint64(req.Height)) +// MissedBlocks returns missed blocks by validator public key. +func (s *Service) MissedBlocks(ctx context.Context, req *pb.MissedBlocksRequest) (*pb.MissedBlocksResponse, error) { + if !strings.HasPrefix(req.PublicKey, "Mp") { + return nil, status.Error(codes.InvalidArgument, "public key don't has prefix 'Mp'") + } + + cState, err := s.blockchain.GetStateForHeight(req.Height) if err != nil { - return new(pb.MissedBlocksResponse), err + return nil, status.Error(codes.NotFound, err.Error()) + } + + if req.Height != 0 { + cState.Lock() + cState.Validators().LoadValidators() + cState.Unlock() + } + + cState.RLock() + defer cState.RUnlock() + + val := cState.Validators().GetByPublicKey(types.HexToPubkey(req.PublicKey)) + if val == nil { + return nil, status.Error(codes.NotFound, "Validator not found") } - return &pb.MissedBlocksResponse{MissedBlocks: blocks, MissedBlocksCount: fmt.Sprintf("%d", count)}, nil + return &pb.MissedBlocksResponse{ + MissedBlocks: val.AbsentTimes.String(), + MissedBlocksCount: int64(val.CountAbsentTimes()), + }, nil } diff --git a/api/v2/service/net_info.go b/api/v2/service/net_info.go index 28ef10402..f68894789 100644 --- a/api/v2/service/net_info.go +++ b/api/v2/service/net_info.go @@ -2,39 +2,55 @@ package service import ( "context" - "fmt" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "github.com/golang/protobuf/ptypes/empty" + "github.com/tendermint/tendermint/evidence" + "github.com/tendermint/tendermint/p2p" + typesTM "github.com/tendermint/tendermint/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/wrapperspb" "time" ) -func (s *Service) NetInfo(context.Context, *empty.Empty) (*pb.NetInfoResponse, error) { +// NetInfo returns network info. +func (s *Service) NetInfo(ctx context.Context, _ *empty.Empty) (*pb.NetInfoResponse, error) { result, err := s.client.NetInfo() if err != nil { - return new(pb.NetInfoResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, status.Error(codes.FailedPrecondition, err.Error()) } var peers []*pb.NetInfoResponse_Peer for _, peer := range result.Peers { + + var currentHeight *wrapperspb.UInt64Value + peerHeight := peerHeight(s.tmNode.Switch(), peer.NodeInfo.ID()) + if peerHeight != 0 { + currentHeight = wrapperspb.UInt64(uint64(peerHeight)) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + var channels []*pb.NetInfoResponse_Peer_ConnectionStatus_Channel for _, channel := range peer.ConnectionStatus.Channels { channels = append(channels, &pb.NetInfoResponse_Peer_ConnectionStatus_Channel{ - Id: fmt.Sprintf("%d", channel.ID), - SendQueueCapacity: fmt.Sprintf("%d", channel.SendQueueCapacity), - SendQueueSize: fmt.Sprintf("%d", channel.SendQueueSize), - Priority: fmt.Sprintf("%d", channel.Priority), - RecentlySent: fmt.Sprintf("%d", channel.RecentlySent), + Id: int64(channel.ID), + SendQueueCapacity: int64(channel.SendQueueCapacity), + SendQueueSize: int64(channel.SendQueueSize), + Priority: int64(channel.Priority), + RecentlySent: channel.RecentlySent, }) } peers = append(peers, &pb.NetInfoResponse_Peer{ + LatestBlockHeight: currentHeight, NodeInfo: &pb.NodeInfo{ ProtocolVersion: &pb.NodeInfo_ProtocolVersion{ - P2P: fmt.Sprintf("%d", peer.NodeInfo.ProtocolVersion.P2P), - Block: fmt.Sprintf("%d", peer.NodeInfo.ProtocolVersion.Block), - App: fmt.Sprintf("%d", peer.NodeInfo.ProtocolVersion.App), + P2P: uint64(peer.NodeInfo.ProtocolVersion.P2P), + Block: uint64(peer.NodeInfo.ProtocolVersion.Block), + App: uint64(peer.NodeInfo.ProtocolVersion.App), }, Id: string(peer.NodeInfo.ID()), ListenAddr: peer.NodeInfo.ListenAddr, @@ -49,24 +65,38 @@ func (s *Service) NetInfo(context.Context, *empty.Empty) (*pb.NetInfoResponse, e }, IsOutbound: peer.IsOutbound, ConnectionStatus: &pb.NetInfoResponse_Peer_ConnectionStatus{ - Duration: fmt.Sprintf("%d", peer.ConnectionStatus.Duration), + Duration: uint64(peer.ConnectionStatus.Duration), SendMonitor: &pb.NetInfoResponse_Peer_ConnectionStatus_Monitor{ - Active: false, + Active: peer.ConnectionStatus.SendMonitor.Active, Start: peer.ConnectionStatus.SendMonitor.Start.Format(time.RFC3339Nano), - Duration: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.Duration.Nanoseconds()), - Idle: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.Idle.Nanoseconds()), - Bytes: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.Bytes), - Samples: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.Samples), - InstRate: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.InstRate), - CurRate: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.CurRate), - AvgRate: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.AvgRate), - PeakRate: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.PeakRate), - BytesRem: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.BytesRem), - TimeRem: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.TimeRem.Nanoseconds()), - Progress: fmt.Sprintf("%d", peer.ConnectionStatus.SendMonitor.Progress), + Duration: peer.ConnectionStatus.SendMonitor.Duration.Nanoseconds(), + Idle: peer.ConnectionStatus.SendMonitor.Idle.Nanoseconds(), + Bytes: peer.ConnectionStatus.SendMonitor.Bytes, + Samples: peer.ConnectionStatus.SendMonitor.Samples, + InstRate: peer.ConnectionStatus.SendMonitor.InstRate, + CurRate: peer.ConnectionStatus.SendMonitor.CurRate, + AvgRate: peer.ConnectionStatus.SendMonitor.AvgRate, + PeakRate: peer.ConnectionStatus.SendMonitor.PeakRate, + BytesRem: peer.ConnectionStatus.SendMonitor.BytesRem, + TimeRem: peer.ConnectionStatus.SendMonitor.TimeRem.Nanoseconds(), + Progress: uint64(peer.ConnectionStatus.SendMonitor.Progress), + }, + RecvMonitor: &pb.NetInfoResponse_Peer_ConnectionStatus_Monitor{ + Active: peer.ConnectionStatus.RecvMonitor.Active, + Start: peer.ConnectionStatus.RecvMonitor.Start.Format(time.RFC3339Nano), + Duration: peer.ConnectionStatus.RecvMonitor.Duration.Nanoseconds(), + Idle: peer.ConnectionStatus.RecvMonitor.Idle.Nanoseconds(), + Bytes: peer.ConnectionStatus.RecvMonitor.Bytes, + Samples: peer.ConnectionStatus.RecvMonitor.Samples, + InstRate: peer.ConnectionStatus.RecvMonitor.InstRate, + CurRate: peer.ConnectionStatus.RecvMonitor.CurRate, + AvgRate: peer.ConnectionStatus.RecvMonitor.AvgRate, + PeakRate: peer.ConnectionStatus.RecvMonitor.PeakRate, + BytesRem: peer.ConnectionStatus.RecvMonitor.BytesRem, + TimeRem: peer.ConnectionStatus.RecvMonitor.TimeRem.Nanoseconds(), + Progress: uint64(peer.ConnectionStatus.RecvMonitor.Progress), }, - RecvMonitor: nil, - Channels: channels, + Channels: channels, }, RemoteIp: peer.RemoteIP, }) @@ -75,7 +105,23 @@ func (s *Service) NetInfo(context.Context, *empty.Empty) (*pb.NetInfoResponse, e return &pb.NetInfoResponse{ Listening: result.Listening, Listeners: result.Listeners, - CountPeers: fmt.Sprintf("%d", result.NPeers), + CountPeers: int64(result.NPeers), Peers: peers, }, nil } + +func peerHeight(sw *p2p.Switch, id p2p.ID) int64 { + peerTM := sw.Peers().Get(id) + if peerTM == nil { + return 0 + } + ps := peerTM.Get(typesTM.PeerStateKey) + if ps == nil { + return 0 + } + peerState, ok := ps.(evidence.PeerState) + if !ok { + return 0 + } + return peerState.GetHeight() +} diff --git a/api/v2/service/send_transaction.go b/api/v2/service/send_transaction.go index f7d64995e..8c6397427 100644 --- a/api/v2/service/send_transaction.go +++ b/api/v2/service/send_transaction.go @@ -3,7 +3,6 @@ package service import ( "context" "encoding/hex" - "fmt" "github.com/MinterTeam/minter-go-node/core/code" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" abci "github.com/tendermint/tendermint/abci/types" @@ -12,35 +11,33 @@ import ( "github.com/tendermint/tendermint/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "strings" ) -func (s *Service) SendPostTransaction(ctx context.Context, req *pb.SendTransactionRequest) (*pb.SendTransactionResponse, error) { - return s.SendGetTransaction(ctx, req) -} - -func (s *Service) SendGetTransaction(_ context.Context, req *pb.SendTransactionRequest) (*pb.SendTransactionResponse, error) { - if len(req.Tx) < 3 { - return new(pb.SendTransactionResponse), status.Error(codes.InvalidArgument, "invalid tx") +// SendTransaction returns the result of sending signed tx. To ensure that transaction was successfully committed to the blockchain, you need to find the transaction by the hash and ensure that the status code equals to 0. +func (s *Service) SendTransaction(ctx context.Context, req *pb.SendTransactionRequest) (*pb.SendTransactionResponse, error) { + if !strings.HasPrefix(strings.Title(req.GetTx()), "0x") { + return nil, status.Error(codes.InvalidArgument, "invalid transaction") } decodeString, err := hex.DecodeString(req.Tx[2:]) if err != nil { - return new(pb.SendTransactionResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, err.Error()) } - result, err := s.broadcastTxSync(decodeString) - if err != nil { - return new(pb.SendTransactionResponse), status.Error(codes.FailedPrecondition, err.Error()) + result, statusErr := s.broadcastTxSync(decodeString, ctx /*timeout*/) + if statusErr != nil { + return nil, statusErr.Err() } switch result.Code { case code.OK: return &pb.SendTransactionResponse{ - Code: fmt.Sprintf("%d", result.Code), + Code: uint64(result.Code), Log: result.Log, - Hash: result.Hash.String(), + Hash: "Mt" + strings.ToLower(result.Hash.String()), }, nil default: - return new(pb.SendTransactionResponse), s.createError(status.New(codes.InvalidArgument, result.Log), result.Info) + return nil, s.createError(status.New(codes.InvalidArgument, result.Log), result.Info) } } @@ -52,21 +49,33 @@ type ResultBroadcastTx struct { Hash bytes.HexBytes `json:"hash"` } -func (s *Service) broadcastTxSync(tx types.Tx) (*ResultBroadcastTx, error) { +func (s *Service) broadcastTxSync(tx types.Tx, ctx context.Context) (*ResultBroadcastTx, *status.Status) { resCh := make(chan *abci.Response, 1) err := s.tmNode.Mempool().CheckTx(tx, func(res *abci.Response) { resCh <- res }, mempool.TxInfo{}) if err != nil { - return nil, err + if err.Error() == mempool.ErrTxInCache.Error() { + return nil, status.New(codes.AlreadyExists, err.Error()) + } + return nil, status.New(codes.FailedPrecondition, err.Error()) } - res := <-resCh - r := res.GetCheckTx() - return &ResultBroadcastTx{ - Code: r.Code, - Data: r.Data, - Log: r.Log, - Info: r.Info, - Hash: tx.Hash(), - }, nil + + select { + case res := <-resCh: + r := res.GetCheckTx() + return &ResultBroadcastTx{ + Code: r.Code, + Data: r.Data, + Log: r.Log, + Info: r.Info, + Hash: tx.Hash(), + }, nil + case <-ctx.Done(): + if ctx.Err() != context.DeadlineExceeded { + return nil, status.New(codes.Canceled, ctx.Err().Error()) + } + return nil, status.New(codes.DeadlineExceeded, ctx.Err().Error()) + } + } diff --git a/api/v2/service/service.go b/api/v2/service/service.go index 360f408b4..c4f04e042 100644 --- a/api/v2/service/service.go +++ b/api/v2/service/service.go @@ -1,18 +1,17 @@ package service import ( - "bytes" + "context" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/minter" - "github.com/MinterTeam/minter-go-node/core/state" - "github.com/golang/protobuf/jsonpb" - _struct "github.com/golang/protobuf/ptypes/struct" "github.com/tendermint/go-amino" tmNode "github.com/tendermint/tendermint/node" - rpc "github.com/tendermint/tendermint/rpc/client" + rpc "github.com/tendermint/tendermint/rpc/client/local" "google.golang.org/grpc/status" + "time" ) +// Service is gRPC implementation ApiServiceServer type Service struct { cdc *amino.Codec blockchain *minter.Blockchain @@ -22,20 +21,21 @@ type Service struct { version string } +// NewService create gRPC server implementation func NewService(cdc *amino.Codec, blockchain *minter.Blockchain, client *rpc.Local, node *tmNode.Node, minterCfg *config.Config, version string) *Service { - return &Service{cdc: cdc, blockchain: blockchain, client: client, minterCfg: minterCfg, version: version, tmNode: node} -} - -func (s *Service) getStateForHeight(height int32) (*state.State, error) { - if height > 0 { - cState, err := s.blockchain.GetStateForHeight(uint64(height)) - if err != nil { - return nil, err - } - return cState, nil + return &Service{ + cdc: cdc, + blockchain: blockchain, + client: client, + minterCfg: minterCfg, + version: version, + tmNode: node, } +} - return s.blockchain.CurrentState(), nil +// TimeoutDuration gRPC +func (s *Service) TimeoutDuration() time.Duration { + return s.minterCfg.APIv2TimeoutDuration } func (s *Service) createError(statusErr *status.Status, data string) error { @@ -43,14 +43,8 @@ func (s *Service) createError(statusErr *status.Status, data string) error { return statusErr.Err() } - var bb bytes.Buffer - if _, err := bb.Write([]byte(data)); err != nil { - s.client.Logger.Error(err.Error()) - return statusErr.Err() - } - - detailsMap := &_struct.Struct{Fields: make(map[string]*_struct.Value)} - if err := (&jsonpb.Unmarshaler{}).Unmarshal(&bb, detailsMap); err != nil { + detailsMap, err := encodeToStruct([]byte(data)) + if err != nil { s.client.Logger.Error(err.Error()) return statusErr.Err() } @@ -63,3 +57,12 @@ func (s *Service) createError(statusErr *status.Status, data string) error { return withDetails.Err() } + +func (s *Service) checkTimeout(ctx context.Context) *status.Status { + select { + case <-ctx.Done(): + return status.FromContextError(ctx.Err()) + default: + return nil + } +} diff --git a/api/v2/service/status.go b/api/v2/service/status.go index 80b452b31..98e967d72 100644 --- a/api/v2/service/status.go +++ b/api/v2/service/status.go @@ -10,19 +10,26 @@ import ( "time" ) +// Status returns current min gas price. func (s *Service) Status(context.Context, *empty.Empty) (*pb.StatusResponse, error) { result, err := s.client.Status() if err != nil { - return new(pb.StatusResponse), status.Error(codes.Internal, err.Error()) + return nil, status.Error(codes.Internal, err.Error()) } + cState := s.blockchain.CurrentState() + cState.RLock() + defer cState.RUnlock() + return &pb.StatusResponse{ Version: s.version, + Network: result.NodeInfo.Network, LatestBlockHash: fmt.Sprintf("%X", result.SyncInfo.LatestBlockHash), LatestAppHash: fmt.Sprintf("%X", result.SyncInfo.LatestAppHash), - LatestBlockHeight: fmt.Sprintf("%d", result.SyncInfo.LatestBlockHeight), + LatestBlockHeight: uint64(result.SyncInfo.LatestBlockHeight), LatestBlockTime: result.SyncInfo.LatestBlockTime.Format(time.RFC3339Nano), - KeepLastStates: fmt.Sprintf("%d", s.minterCfg.BaseConfig.KeepLastStates), + KeepLastStates: uint64(s.minterCfg.BaseConfig.KeepLastStates), + TotalSlashed: cState.App().GetTotalSlashed().String(), CatchingUp: result.SyncInfo.CatchingUp, PublicKey: fmt.Sprintf("Mp%x", result.ValidatorInfo.PubKey.Bytes()[5:]), NodeId: string(result.NodeInfo.ID()), diff --git a/api/v2/service/transaction.go b/api/v2/service/transaction.go index e55f26c18..e88a76b2f 100644 --- a/api/v2/service/transaction.go +++ b/api/v2/service/transaction.go @@ -1,32 +1,29 @@ package service import ( - "bytes" "context" "encoding/hex" - "errors" "fmt" "github.com/MinterTeam/minter-go-node/core/transaction" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" - "github.com/golang/protobuf/jsonpb" - _struct "github.com/golang/protobuf/ptypes/struct" - tmbytes "github.com/tendermint/tendermint/libs/bytes" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "strings" ) -func (s *Service) Transaction(_ context.Context, req *pb.TransactionRequest) (*pb.TransactionResponse, error) { +// Transaction returns transaction info. +func (s *Service) Transaction(ctx context.Context, req *pb.TransactionRequest) (*pb.TransactionResponse, error) { if len(req.Hash) < 3 { - return new(pb.TransactionResponse), status.Error(codes.InvalidArgument, "invalid hash") + return nil, status.Error(codes.InvalidArgument, "invalid hash") } decodeString, err := hex.DecodeString(req.Hash[2:]) if err != nil { - return new(pb.TransactionResponse), status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.InvalidArgument, err.Error()) } tx, err := s.client.Tx(decodeString, false) if err != nil { - return new(pb.TransactionResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, status.Error(codes.FailedPrecondition, err.Error()) } decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) @@ -37,79 +34,38 @@ func (s *Service) Transaction(_ context.Context, req *pb.TransactionRequest) (*p tags[string(tag.Key)] = string(tag.Value) } - dataStruct, err := s.encodeTxData(decodedTx) - if err != nil { - return new(pb.TransactionResponse), status.Error(codes.FailedPrecondition, err.Error()) - } + cState := s.blockchain.CurrentState() - return &pb.TransactionResponse{ - Hash: tmbytes.HexBytes(tx.Tx.Hash()).String(), - RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), - Height: fmt.Sprintf("%d", tx.Height), - Index: fmt.Sprintf("%d", tx.Index), - From: sender.String(), - Nonce: fmt.Sprintf("%d", decodedTx.Nonce), - GasPrice: fmt.Sprintf("%d", decodedTx.GasPrice), - GasCoin: decodedTx.GasCoin.String(), - Gas: fmt.Sprintf("%d", decodedTx.Gas()), - Type: fmt.Sprintf("%d", uint8(decodedTx.Type)), - Data: dataStruct, - Payload: decodedTx.Payload, - Tags: tags, - Code: fmt.Sprintf("%d", tx.TxResult.Code), - Log: tx.TxResult.Log, - }, nil -} + cState.RLock() + defer cState.RUnlock() -func (s *Service) encodeTxData(decodedTx *transaction.Transaction) (*_struct.Struct, error) { - var ( - err error - b []byte - bb bytes.Buffer - ) - switch decodedTx.Type { - case transaction.TypeSend: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SendData)) - case transaction.TypeRedeemCheck: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.RedeemCheckData)) - case transaction.TypeSellCoin: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SellCoinData)) - case transaction.TypeSellAllCoin: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SellAllCoinData)) - case transaction.TypeBuyCoin: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.BuyCoinData)) - case transaction.TypeCreateCoin: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.CreateCoinData)) - case transaction.TypeDeclareCandidacy: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.DeclareCandidacyData)) - case transaction.TypeDelegate: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.DelegateData)) - case transaction.TypeSetCandidateOnline: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SetCandidateOnData)) - case transaction.TypeSetCandidateOffline: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.SetCandidateOffData)) - case transaction.TypeUnbond: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.UnbondData)) - case transaction.TypeMultisend: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.MultisendData)) - case transaction.TypeCreateMultisig: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.CreateMultisigData)) - case transaction.TypeEditCandidate: - b, err = s.cdc.MarshalJSON(decodedTx.GetDecodedData().(*transaction.EditCandidateData)) - default: - return nil, errors.New("unknown tx type") + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } + dataStruct, err := encode(decodedTx.GetDecodedData(), cState.Coins()) if err != nil { - return nil, err + return nil, status.Error(codes.Internal, err.Error()) } - bb.Write(b) - - dataStruct := &_struct.Struct{Fields: make(map[string]*_struct.Value)} - if err := (&jsonpb.Unmarshaler{}).Unmarshal(&bb, dataStruct); err != nil { - return nil, err - } - - return dataStruct, nil + return &pb.TransactionResponse{ + Hash: "Mt" + strings.ToLower(hex.EncodeToString(tx.Tx.Hash())), + RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), + Height: uint64(tx.Height), + Index: uint64(tx.Index), + From: sender.String(), + Nonce: decodedTx.Nonce, + GasPrice: uint64(decodedTx.GasPrice), + GasCoin: &pb.Coin{ + Id: uint64(decodedTx.GasCoin), + Symbol: cState.Coins().GetCoin(decodedTx.GasCoin).GetFullSymbol(), + }, + Gas: uint64(decodedTx.Gas()), + Type: uint64(decodedTx.Type), + Data: dataStruct, + Payload: decodedTx.Payload, + Tags: tags, + Code: uint64(tx.TxResult.Code), + Log: tx.TxResult.Log, + }, nil } diff --git a/api/v2/service/transactions.go b/api/v2/service/transactions.go index cd7429024..bfe5cbd5d 100644 --- a/api/v2/service/transactions.go +++ b/api/v2/service/transactions.go @@ -2,63 +2,71 @@ package service import ( "context" + "encoding/hex" "fmt" "github.com/MinterTeam/minter-go-node/core/transaction" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" - "github.com/tendermint/tendermint/libs/bytes" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "strings" ) -func (s *Service) Transactions(_ context.Context, req *pb.TransactionsRequest) (*pb.TransactionsResponse, error) { - page := int(req.Page) - if page == 0 { - page = 1 - } - perPage := int(req.PerPage) - if perPage == 0 { - perPage = 100 - } - - rpcResult, err := s.client.TxSearch(req.Query, false, page, perPage, "desc") +// Transactions return transactions by query. +func (s *Service) Transactions(ctx context.Context, req *pb.TransactionsRequest) (*pb.TransactionsResponse, error) { + rpcResult, err := s.client.TxSearch(req.Query, false, int(req.Page), int(req.PerPage), "desc") if err != nil { - return new(pb.TransactionsResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, status.Error(codes.FailedPrecondition, err.Error()) } - result := make([]*pb.TransactionResponse, 0, len(rpcResult.Txs)) - for _, tx := range rpcResult.Txs { - decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) - sender, _ := decodedTx.Sender() + lenTx := len(rpcResult.Txs) + result := make([]*pb.TransactionResponse, 0, lenTx) + if lenTx != 0 { - tags := make(map[string]string) - for _, tag := range tx.TxResult.Events[0].Attributes { - tags[string(tag.Key)] = string(tag.Value) - } + cState := s.blockchain.CurrentState() + cState.RLock() + defer cState.RUnlock() - dataStruct, err := s.encodeTxData(decodedTx) - if err != nil { - return new(pb.TransactionsResponse), status.Error(codes.FailedPrecondition, err.Error()) - } + for _, tx := range rpcResult.Txs { - result = append(result, &pb.TransactionResponse{ - Hash: bytes.HexBytes(tx.Tx.Hash()).String(), - RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), - Height: fmt.Sprintf("%d", tx.Height), - Index: fmt.Sprintf("%d", tx.Index), - From: sender.String(), - Nonce: fmt.Sprintf("%d", decodedTx.Nonce), - GasPrice: fmt.Sprintf("%d", decodedTx.GasPrice), - GasCoin: decodedTx.GasCoin.String(), - Gas: fmt.Sprintf("%d", decodedTx.Gas()), - Type: fmt.Sprintf("%d", uint8(decodedTx.Type)), - Data: dataStruct, - Payload: decodedTx.Payload, - Tags: tags, - Code: fmt.Sprintf("%d", tx.TxResult.Code), - Log: tx.TxResult.Log, - }) - } + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + decodedTx, _ := transaction.TxDecoder.DecodeFromBytes(tx.Tx) + sender, _ := decodedTx.Sender() + + tags := make(map[string]string) + for _, tag := range tx.TxResult.Events[0].Attributes { + tags[string(tag.Key)] = string(tag.Value) + } + + data, err := encode(decodedTx.GetDecodedData(), cState.Coins()) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + result = append(result, &pb.TransactionResponse{ + Hash: "Mt" + strings.ToLower(hex.EncodeToString(tx.Tx.Hash())), + RawTx: fmt.Sprintf("%x", []byte(tx.Tx)), + Height: uint64(tx.Height), + Index: uint64(tx.Index), + From: sender.String(), + Nonce: decodedTx.Nonce, + GasPrice: uint64(decodedTx.GasPrice), + GasCoin: &pb.Coin{ + Id: uint64(decodedTx.GasCoin), + Symbol: cState.Coins().GetCoin(decodedTx.GasCoin).GetFullSymbol(), + }, + Gas: uint64(decodedTx.Gas()), + Type: uint64(uint8(decodedTx.Type)), + Data: data, + Payload: decodedTx.Payload, + Tags: tags, + Code: uint64(tx.TxResult.Code), + Log: tx.TxResult.Log, + }) + } + } return &pb.TransactionsResponse{ Transactions: result, }, nil diff --git a/api/v2/service/unconfirmed_txs.go b/api/v2/service/unconfirmed_txs.go index 32f7a6688..7bf9778e5 100644 --- a/api/v2/service/unconfirmed_txs.go +++ b/api/v2/service/unconfirmed_txs.go @@ -2,20 +2,25 @@ package service import ( "context" - "fmt" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -func (s *Service) UnconfirmedTxs(_ context.Context, req *pb.UnconfirmedTxsRequest) (*pb.UnconfirmedTxsResponse, error) { +// UnconfirmedTxs returns unconfirmed transactions. +func (s *Service) UnconfirmedTxs(ctx context.Context, req *pb.UnconfirmedTxsRequest) (*pb.UnconfirmedTxsResponse, error) { txs, err := s.client.UnconfirmedTxs(int(req.Limit)) if err != nil { - return new(pb.UnconfirmedTxsResponse), status.Error(codes.Internal, err.Error()) + return nil, status.Error(codes.Internal, err.Error()) + } + transactions := make([]string, 0, len(txs.Txs)) + for _, tx := range txs.Txs { + transactions = append(transactions, tx.String()) } return &pb.UnconfirmedTxsResponse{ - TransactionsCount: fmt.Sprintf("%d", txs.Count), - TotalTransactions: fmt.Sprintf("%d", txs.Total), - TotalBytes: fmt.Sprintf("%d", txs.TotalBytes), + TransactionCount: uint64(txs.Count), + TotalTransactions: uint64(txs.Total), + TotalBytes: uint64(txs.TotalBytes), + Transactions: transactions, }, nil } diff --git a/api/v2/service/validators.go b/api/v2/service/validators.go index 57bbbfbcd..7c7334b80 100644 --- a/api/v2/service/validators.go +++ b/api/v2/service/validators.go @@ -2,22 +2,26 @@ package service import ( "context" - "fmt" "github.com/MinterTeam/minter-go-node/core/types" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -func (s *Service) Validators(_ context.Context, req *pb.ValidatorsRequest) (*pb.ValidatorsResponse, error) { - height := req.Height +// Validators returns list of active validators. +func (s *Service) Validators(ctx context.Context, req *pb.ValidatorsRequest) (*pb.ValidatorsResponse, error) { + height := int64(req.Height) if height == 0 { height = int64(s.blockchain.Height()) } - tmVals, err := s.client.Validators(&height, int(req.Page), int(req.PerPage)) + tmVals, err := s.client.Validators(&height, 1, 100) if err != nil { - return new(pb.ValidatorsResponse), status.Error(codes.FailedPrecondition, err.Error()) + return nil, status.Error(codes.Internal, err.Error()) + } + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() } responseValidators := make([]*pb.ValidatorsResponse_Result, 0, len(tmVals.Validators)) @@ -26,7 +30,7 @@ func (s *Service) Validators(_ context.Context, req *pb.ValidatorsRequest) (*pb. copy(pk[:], val.PubKey.Bytes()[5:]) responseValidators = append(responseValidators, &pb.ValidatorsResponse_Result{ PublicKey: pk.String(), - VotingPower: fmt.Sprintf("%d", val.VotingPower), + VotingPower: uint64(val.VotingPower), }) } return &pb.ValidatorsResponse{Validators: responseValidators}, nil diff --git a/api/v2/service/waitlist.go b/api/v2/service/waitlist.go new file mode 100644 index 000000000..d94361f27 --- /dev/null +++ b/api/v2/service/waitlist.go @@ -0,0 +1,77 @@ +package service + +import ( + "context" + "encoding/hex" + "github.com/MinterTeam/minter-go-node/core/state/waitlist" + "github.com/MinterTeam/minter-go-node/core/types" + pb "github.com/MinterTeam/node-grpc-gateway/api_pb" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "strings" +) + +// WaitList returns the list of address stakes in waitlist. +func (s *Service) WaitList(ctx context.Context, req *pb.WaitListRequest) (*pb.WaitListResponse, error) { + if !strings.HasPrefix(strings.Title(req.Address), "Mx") { + return nil, status.Error(codes.InvalidArgument, "invalid address") + } + + decodeString, err := hex.DecodeString(req.Address[2:]) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid address") + } + + address := types.BytesToAddress(decodeString) + + cState, err := s.blockchain.GetStateForHeight(req.Height) + if err != nil { + return nil, status.Error(codes.NotFound, err.Error()) + } + + if req.Height != 0 { + cState.Lock() + cState.Candidates().LoadCandidates() + cState.Unlock() + } + + cState.RLock() + defer cState.RUnlock() + + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + response := new(pb.WaitListResponse) + var items []waitlist.Item + publicKey := req.PublicKey + if publicKey != "" { + if !strings.HasPrefix(publicKey, "Mp") { + return nil, status.Error(codes.InvalidArgument, "public key don't has prefix 'Mp'") + } + items = cState.WaitList().GetByAddressAndPubKey(address, types.HexToPubkey(publicKey)) + } else { + model := cState.WaitList().GetByAddress(address) + if model == nil { + return response, nil + } + items = model.List + } + response.List = make([]*pb.WaitListResponse_Wait, 0, len(items)) + for _, item := range items { + if timeoutStatus := s.checkTimeout(ctx); timeoutStatus != nil { + return nil, timeoutStatus.Err() + } + + response.List = append(response.List, &pb.WaitListResponse_Wait{ + PublicKey: cState.Candidates().PubKey(item.CandidateId).String(), + Coin: &pb.Coin{ + Id: uint64(item.Coin), + Symbol: cState.Coins().GetCoin(item.Coin).GetFullSymbol(), + }, + Value: item.Value.String(), + }) + } + + return response, nil +} diff --git a/api/v2/service/ws.go b/api/v2/service/ws.go index 4387f134f..2ec046fd1 100644 --- a/api/v2/service/ws.go +++ b/api/v2/service/ws.go @@ -1,49 +1,52 @@ package service import ( - "bytes" "context" - "encoding/json" "fmt" pb "github.com/MinterTeam/node-grpc-gateway/api_pb" - "github.com/golang/protobuf/jsonpb" - _struct "github.com/golang/protobuf/ptypes/struct" "github.com/google/uuid" core_types "github.com/tendermint/tendermint/rpc/core/types" "google.golang.org/grpc/codes" + "google.golang.org/grpc/peer" "google.golang.org/grpc/status" "time" ) const ( - SubscribeTimeout = 5 * time.Second + subscribeTimeout = 15 * time.Second ) +// Subscribe returns a subscription for events by query. func (s *Service) Subscribe(request *pb.SubscribeRequest, stream pb.ApiService_SubscribeServer) error { if s.client.NumClients() >= s.minterCfg.RPC.MaxSubscriptionClients { - return status.Error(codes.Internal, fmt.Sprintf("max_subscription_clients %d reached", s.minterCfg.RPC.MaxSubscriptionClients)) + return status.Error(codes.ResourceExhausted, fmt.Sprintf("max_subscription_clients %d reached", s.minterCfg.RPC.MaxSubscriptionClients)) } s.client.Logger.Info("Subscribe to query", "query", request.Query) - subCtx, cancel := context.WithTimeout(stream.Context(), SubscribeTimeout) + ctx, cancel := context.WithTimeout(stream.Context(), subscribeTimeout) defer cancel() - subscriber := uuid.New().String() - sub, err := s.client.Subscribe(subCtx, subscriber, request.Query) + + remote := uuid.New().String() + subscriber, ok := peer.FromContext(ctx) + if ok { + remote = subscriber.Addr.String() + } + sub, err := s.client.Subscribe(ctx, remote, request.Query) if err != nil { - return status.Error(codes.Internal, err.Error()) + return status.Error(codes.InvalidArgument, err.Error()) } defer func() { - if err := s.client.UnsubscribeAll(stream.Context(), subscriber); err != nil { + if err := s.client.UnsubscribeAll(context.Background(), remote); err != nil { s.client.Logger.Error(err.Error()) } }() for { select { - case <-stream.Context().Done(): - return stream.Context().Err() + case <-ctx.Done(): + return status.FromContextError(ctx.Err()).Err() case msg, ok := <-sub: if !ok { return nil @@ -60,23 +63,16 @@ func (s *Service) Subscribe(request *pb.SubscribeRequest, stream pb.ApiService_S } func subscribeResponse(msg core_types.ResultEvent) (*pb.SubscribeResponse, error) { - var events []*pb.SubscribeResponse_Event + events := make([]*pb.SubscribeResponse_Event, 0, len(msg.Events)) for key, eventSlice := range msg.Events { events = append(events, &pb.SubscribeResponse_Event{ Key: key, Events: eventSlice, }) } - byteData, err := json.Marshal(msg.Data) - if err != nil { - return nil, err - } - - var bb bytes.Buffer - bb.Write(byteData) - data := &_struct.Struct{Fields: make(map[string]*_struct.Value)} - if err := (&jsonpb.Unmarshaler{}).Unmarshal(&bb, data); err != nil { + data, err := toStruct(msg.Data) + if err != nil { return nil, err } diff --git a/api/v2/v2.go b/api/v2/v2.go index a2667aeb1..166b1cb66 100644 --- a/api/v2/v2.go +++ b/api/v2/v2.go @@ -2,27 +2,64 @@ package v2 import ( "context" + "encoding/json" + "fmt" "github.com/MinterTeam/minter-go-node/api/v2/service" gw "github.com/MinterTeam/node-grpc-gateway/api_pb" + _ "github.com/MinterTeam/node-grpc-gateway/statik" + kit_log "github.com/go-kit/kit/log" + "github.com/gorilla/handlers" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/kit" + grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" + grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/rakyll/statik/fs" + "github.com/tendermint/tendermint/libs/log" "github.com/tmc/grpc-websocket-proxy/wsproxy" "golang.org/x/sync/errgroup" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" + _struct "google.golang.org/protobuf/types/known/structpb" + "mime" "net" "net/http" + "strconv" + "strings" + "time" ) -func Run(srv *service.Service, addrGRPC, addrApi string) error { +// Run initialises gRPC and API v2 interfaces +func Run(srv *service.Service, addrGRPC, addrApi string, logger log.Logger) error { lis, err := net.Listen("tcp", addrGRPC) if err != nil { return err } + kitLogger := &kitLogger{logger} + + loggerOpts := []kit.Option{ + kit.WithLevels(func(code codes.Code, logger kit_log.Logger) kit_log.Logger { return logger }), + } grpcServer := grpc.NewServer( - grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor), - grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor), + grpc_middleware.WithStreamServerChain( + grpc_prometheus.StreamServerInterceptor, + grpc_recovery.StreamServerInterceptor(), + grpc_ctxtags.StreamServerInterceptor(requestExtractorFields()), + kit.StreamServerInterceptor(kitLogger, loggerOpts...), + ), + grpc_middleware.WithUnaryServerChain( + grpc_prometheus.UnaryServerInterceptor, + grpc_recovery.UnaryServerInterceptor(), + grpc_ctxtags.UnaryServerInterceptor(requestExtractorFields()), + kit.UnaryServerInterceptor(kitLogger, loggerOpts...), + unaryTimeoutInterceptor(srv.TimeoutDuration()), + ), ) + runtime.GlobalHTTPErrorHandler = httpError gw.RegisterApiServiceServer(grpcServer, srv) grpc_prometheus.Register(grpcServer) @@ -35,21 +72,146 @@ func Run(srv *service.Service, addrGRPC, addrApi string) error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() - mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true})) + gwmux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName: true, EmitDefaults: true}), + ) opts := []grpc.DialOption{ grpc.WithInsecure(), - grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(50000000)), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(200000000)), } - - group.Go(func() error { - return gw.RegisterApiServiceHandlerFromEndpoint(ctx, mux, addrGRPC, opts) + err = gw.RegisterApiServiceHandlerFromEndpoint(ctx, gwmux, addrGRPC, opts) + if err != nil { + return err + } + mux := http.NewServeMux() + openapi := "/v2/openapi-ui/" + _ = serveOpenAPI(openapi, mux) + mux.HandleFunc("/v2/", func(writer http.ResponseWriter, request *http.Request) { + if request.URL.Path == "/v2/" { + http.Redirect(writer, request, openapi, 302) + return + } + http.StripPrefix("/v2", handlers.CompressHandler(allowCORS(wsproxy.WebsocketProxy(gwmux)))).ServeHTTP(writer, request) }) group.Go(func() error { return http.ListenAndServe(addrApi, mux) }) - group.Go(func() error { - return http.ListenAndServe(addrApi, wsproxy.WebsocketProxy(mux)) - }) return group.Wait() } + +func allowCORS(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if origin := r.Header.Get("Origin"); origin != "" { + w.Header().Set("Access-Control-Allow-Origin", origin) + if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { + preflightHandler(w, r) + return + } + } + h.ServeHTTP(w, r) + }) +} + +func preflightHandler(w http.ResponseWriter, _ *http.Request) { + headers := []string{"Content-Type", "Accept"} + w.Header().Set("Access-Control-Allow-Headers", strings.Join(headers, ",")) + methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE"} + w.Header().Set("Access-Control-Allow-Methods", strings.Join(methods, ",")) +} + +func serveOpenAPI(prefix string, mux *http.ServeMux) error { + _ = mime.AddExtensionType(".svg", "image/svg+xml") + + statikFS, err := fs.New() + if err != nil { + return err + } + + // Expose files in static on /v2/openapi-ui + fileServer := http.FileServer(statikFS) + mux.Handle(prefix, http.StripPrefix(prefix, fileServer)) + return nil +} + +func unaryTimeoutInterceptor(timeout time.Duration) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + withTimeout, _ := context.WithTimeout(ctx, timeout) + return handler(withTimeout, req) + } +} + +func parseStatus(s *status.Status) (string, map[string]string) { + codeString := strconv.Itoa(runtime.HTTPStatusFromCode(s.Code())) + dataString := map[string]string{} + details := s.Details() + if len(details) == 0 { + return codeString, dataString + } + + detail, ok := details[0].(*_struct.Struct) + if !ok { + return codeString, dataString + } + + data := detail.AsMap() + for k, v := range data { + dataString[k] = fmt.Sprintf("%s", v) + } + code, ok := detail.GetFields()["code"] + if ok { + codeString = code.GetStringValue() + } + return codeString, dataString +} + +func httpError(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, r *http.Request, err error) { + const fallback = `{"error": {"code": "500", "message": "failed to marshal error message"}}` + + contentType := marshaler.ContentType() + w.Header().Set("Content-Type", contentType) + + s, ok := status.FromError(err) + if !ok { + s = status.New(codes.Unknown, err.Error()) + } + st := runtime.HTTPStatusFromCode(s.Code()) + w.WriteHeader(st) + + codeString, data := parseStatus(s) + delete(data, "code") + + jErr := json.NewEncoder(w).Encode(gw.ErrorBody{ + Error: &gw.ErrorBody_Error{ + Code: codeString, + Message: s.Message(), + Data: data, + }, + }) + + if jErr != nil { + grpclog.Infof("Failed to write response: %v", err) + w.Write([]byte(fallback)) + } +} + +func requestExtractorFields() grpc_ctxtags.Option { + return grpc_ctxtags.WithFieldExtractorForInitialReq(func(fullMethod string, req interface{}) map[string]interface{} { + retMap := make(map[string]interface{}) + marshal, _ := json.Marshal(req) + _ = json.Unmarshal(marshal, &retMap) + if len(retMap) == 0 { + return nil + } + return retMap + }) +} + +type kitLogger struct { + log.Logger +} + +func (l *kitLogger) Log(keyvals ...interface{}) error { + l.Info("API", keyvals...) + return nil +} diff --git a/api/validators.go b/api/validators.go index f744c179b..b370fc082 100644 --- a/api/validators.go +++ b/api/validators.go @@ -11,13 +11,13 @@ type ValidatorResponse struct { type ResponseValidators []ValidatorResponse -func Validators(height uint64, page, perPage int) (*ResponseValidators, error) { +func Validators(height uint64) (*ResponseValidators, error) { if height == 0 { height = blockchain.Height() } h := int64(height) - tmVals, err := client.Validators(&h, page, perPage) + tmVals, err := client.Validators(&h, 1, 100) if err != nil { return nil, err } diff --git a/api/waitlist.go b/api/waitlist.go new file mode 100644 index 000000000..0ebb6accb --- /dev/null +++ b/api/waitlist.go @@ -0,0 +1,39 @@ +package api + +import ( + "github.com/MinterTeam/minter-go-node/core/types" +) + +type WaitlistResponse struct { + List []*Wait `json:"list"` +} + +type Wait struct { + Coin Coin `json:"coin"` + Value string `json:"value"` +} + +func Waitlist(pubkey types.Pubkey, address types.Address, height int) (*WaitlistResponse, error) { + cState, err := GetStateForHeight(height) + if err != nil { + return nil, err + } + + cState.RLock() + defer cState.RUnlock() + + response := new(WaitlistResponse) + items := cState.WaitList().GetByAddressAndPubKey(address, pubkey) + response.List = make([]*Wait, 0, len(items)) + for _, item := range items { + response.List = append(response.List, &Wait{ + Coin: Coin{ + ID: item.Coin.Uint32(), + Symbol: cState.Coins().GetCoin(item.Coin).GetFullSymbol(), + }, + Value: item.Value.String(), + }) + } + + return response, nil +} diff --git a/cli/pb/generate.sh b/cli/cli_pb/generate.sh similarity index 100% rename from cli/pb/generate.sh rename to cli/cli_pb/generate.sh diff --git a/cli/cli_pb/manager.pb.go b/cli/cli_pb/manager.pb.go new file mode 100644 index 000000000..a49bfac53 --- /dev/null +++ b/cli/cli_pb/manager.pb.go @@ -0,0 +1,1910 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.13.0 +// source: manager.proto + +package cli_pb + +import ( + context "context" + proto "github.com/golang/protobuf/proto" + empty "github.com/golang/protobuf/ptypes/empty" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + _ "github.com/golang/protobuf/ptypes/wrappers" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type DashboardResponse_ValidatorStatus int32 + +const ( + DashboardResponse_Validating DashboardResponse_ValidatorStatus = 0 + DashboardResponse_Challenger DashboardResponse_ValidatorStatus = 1 + DashboardResponse_Offline DashboardResponse_ValidatorStatus = 2 + DashboardResponse_NotDeclared DashboardResponse_ValidatorStatus = 3 +) + +// Enum value maps for DashboardResponse_ValidatorStatus. +var ( + DashboardResponse_ValidatorStatus_name = map[int32]string{ + 0: "Validating", + 1: "Challenger", + 2: "Offline", + 3: "NotDeclared", + } + DashboardResponse_ValidatorStatus_value = map[string]int32{ + "Validating": 0, + "Challenger": 1, + "Offline": 2, + "NotDeclared": 3, + } +) + +func (x DashboardResponse_ValidatorStatus) Enum() *DashboardResponse_ValidatorStatus { + p := new(DashboardResponse_ValidatorStatus) + *p = x + return p +} + +func (x DashboardResponse_ValidatorStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (DashboardResponse_ValidatorStatus) Descriptor() protoreflect.EnumDescriptor { + return file_manager_proto_enumTypes[0].Descriptor() +} + +func (DashboardResponse_ValidatorStatus) Type() protoreflect.EnumType { + return &file_manager_proto_enumTypes[0] +} + +func (x DashboardResponse_ValidatorStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use DashboardResponse_ValidatorStatus.Descriptor instead. +func (DashboardResponse_ValidatorStatus) EnumDescriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{5, 0} +} + +type NodeInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProtocolVersion *NodeInfo_ProtocolVersion `protobuf:"bytes,8,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ListenAddr string `protobuf:"bytes,2,opt,name=listen_addr,json=listenAddr,proto3" json:"listen_addr,omitempty"` + Network string `protobuf:"bytes,3,opt,name=network,proto3" json:"network,omitempty"` + Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` + Channels string `protobuf:"bytes,5,opt,name=channels,proto3" json:"channels,omitempty"` + Moniker string `protobuf:"bytes,6,opt,name=moniker,proto3" json:"moniker,omitempty"` + Other *NodeInfo_Other `protobuf:"bytes,7,opt,name=other,proto3" json:"other,omitempty"` +} + +func (x *NodeInfo) Reset() { + *x = NodeInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeInfo) ProtoMessage() {} + +func (x *NodeInfo) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. +func (*NodeInfo) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{0} +} + +func (x *NodeInfo) GetProtocolVersion() *NodeInfo_ProtocolVersion { + if x != nil { + return x.ProtocolVersion + } + return nil +} + +func (x *NodeInfo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *NodeInfo) GetListenAddr() string { + if x != nil { + return x.ListenAddr + } + return "" +} + +func (x *NodeInfo) GetNetwork() string { + if x != nil { + return x.Network + } + return "" +} + +func (x *NodeInfo) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *NodeInfo) GetChannels() string { + if x != nil { + return x.Channels + } + return "" +} + +func (x *NodeInfo) GetMoniker() string { + if x != nil { + return x.Moniker + } + return "" +} + +func (x *NodeInfo) GetOther() *NodeInfo_Other { + if x != nil { + return x.Other + } + return nil +} + +type NetInfoResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Listening bool `protobuf:"varint,4,opt,name=listening,proto3" json:"listening,omitempty"` + Listeners []string `protobuf:"bytes,1,rep,name=listeners,proto3" json:"listeners,omitempty"` + CountPeers int64 `protobuf:"varint,2,opt,name=count_peers,json=countPeers,proto3" json:"count_peers,omitempty"` + Peers []*NetInfoResponse_Peer `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` +} + +func (x *NetInfoResponse) Reset() { + *x = NetInfoResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NetInfoResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetInfoResponse) ProtoMessage() {} + +func (x *NetInfoResponse) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetInfoResponse.ProtoReflect.Descriptor instead. +func (*NetInfoResponse) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{1} +} + +func (x *NetInfoResponse) GetListening() bool { + if x != nil { + return x.Listening + } + return false +} + +func (x *NetInfoResponse) GetListeners() []string { + if x != nil { + return x.Listeners + } + return nil +} + +func (x *NetInfoResponse) GetCountPeers() int64 { + if x != nil { + return x.CountPeers + } + return 0 +} + +func (x *NetInfoResponse) GetPeers() []*NetInfoResponse_Peer { + if x != nil { + return x.Peers + } + return nil +} + +type StatusResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Version string `protobuf:"bytes,8,opt,name=version,proto3" json:"version,omitempty"` + LatestBlockHash string `protobuf:"bytes,1,opt,name=latest_block_hash,json=latestBlockHash,proto3" json:"latest_block_hash,omitempty"` + LatestAppHash string `protobuf:"bytes,2,opt,name=latest_app_hash,json=latestAppHash,proto3" json:"latest_app_hash,omitempty"` + LatestBlockHeight string `protobuf:"bytes,3,opt,name=latest_block_height,json=latestBlockHeight,proto3" json:"latest_block_height,omitempty"` + LatestBlockTime string `protobuf:"bytes,4,opt,name=latest_block_time,json=latestBlockTime,proto3" json:"latest_block_time,omitempty"` + KeepLastStates string `protobuf:"bytes,5,opt,name=keep_last_states,json=keepLastStates,proto3" json:"keep_last_states,omitempty"` + CatchingUp bool `protobuf:"varint,6,opt,name=catching_up,json=catchingUp,proto3" json:"catching_up,omitempty"` + PublicKey string `protobuf:"bytes,7,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + NodeId string `protobuf:"bytes,9,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` +} + +func (x *StatusResponse) Reset() { + *x = StatusResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResponse) ProtoMessage() {} + +func (x *StatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResponse.ProtoReflect.Descriptor instead. +func (*StatusResponse) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{2} +} + +func (x *StatusResponse) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *StatusResponse) GetLatestBlockHash() string { + if x != nil { + return x.LatestBlockHash + } + return "" +} + +func (x *StatusResponse) GetLatestAppHash() string { + if x != nil { + return x.LatestAppHash + } + return "" +} + +func (x *StatusResponse) GetLatestBlockHeight() string { + if x != nil { + return x.LatestBlockHeight + } + return "" +} + +func (x *StatusResponse) GetLatestBlockTime() string { + if x != nil { + return x.LatestBlockTime + } + return "" +} + +func (x *StatusResponse) GetKeepLastStates() string { + if x != nil { + return x.KeepLastStates + } + return "" +} + +func (x *StatusResponse) GetCatchingUp() bool { + if x != nil { + return x.CatchingUp + } + return false +} + +func (x *StatusResponse) GetPublicKey() string { + if x != nil { + return x.PublicKey + } + return "" +} + +func (x *StatusResponse) GetNodeId() string { + if x != nil { + return x.NodeId + } + return "" +} + +type PruneBlocksRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FromHeight int64 `protobuf:"varint,1,opt,name=from_height,json=fromHeight,proto3" json:"from_height,omitempty"` + ToHeight int64 `protobuf:"varint,2,opt,name=to_height,json=toHeight,proto3" json:"to_height,omitempty"` +} + +func (x *PruneBlocksRequest) Reset() { + *x = PruneBlocksRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PruneBlocksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PruneBlocksRequest) ProtoMessage() {} + +func (x *PruneBlocksRequest) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PruneBlocksRequest.ProtoReflect.Descriptor instead. +func (*PruneBlocksRequest) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{3} +} + +func (x *PruneBlocksRequest) GetFromHeight() int64 { + if x != nil { + return x.FromHeight + } + return 0 +} + +func (x *PruneBlocksRequest) GetToHeight() int64 { + if x != nil { + return x.ToHeight + } + return 0 +} + +type DealPeerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Persistent bool `protobuf:"varint,2,opt,name=persistent,proto3" json:"persistent,omitempty"` +} + +func (x *DealPeerRequest) Reset() { + *x = DealPeerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DealPeerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DealPeerRequest) ProtoMessage() {} + +func (x *DealPeerRequest) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DealPeerRequest.ProtoReflect.Descriptor instead. +func (*DealPeerRequest) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{4} +} + +func (x *DealPeerRequest) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *DealPeerRequest) GetPersistent() bool { + if x != nil { + return x.Persistent + } + return false +} + +type DashboardResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LatestHeight int64 `protobuf:"varint,1,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height,omitempty"` + Timestamp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Duration int64 `protobuf:"varint,3,opt,name=duration,proto3" json:"duration,omitempty"` + MemoryUsage uint64 `protobuf:"varint,4,opt,name=memory_usage,json=memoryUsage,proto3" json:"memory_usage,omitempty"` + ValidatorPubKey string `protobuf:"bytes,5,opt,name=validator_pub_key,json=validatorPubKey,proto3" json:"validator_pub_key,omitempty"` + MaxPeerHeight int64 `protobuf:"varint,6,opt,name=max_peer_height,json=maxPeerHeight,proto3" json:"max_peer_height,omitempty"` + PeersCount int32 `protobuf:"varint,7,opt,name=peers_count,json=peersCount,proto3" json:"peers_count,omitempty"` + AvgBlockProcessingTime int64 `protobuf:"varint,8,opt,name=avg_block_processing_time,json=avgBlockProcessingTime,proto3" json:"avg_block_processing_time,omitempty"` + TimePerBlock int64 `protobuf:"varint,9,opt,name=time_per_block,json=timePerBlock,proto3" json:"time_per_block,omitempty"` + MissedBlocks string `protobuf:"bytes,10,opt,name=missed_blocks,json=missedBlocks,proto3" json:"missed_blocks,omitempty"` + VotingPower int64 `protobuf:"varint,11,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` + Stake string `protobuf:"bytes,12,opt,name=stake,proto3" json:"stake,omitempty"` + ValidatorStatus DashboardResponse_ValidatorStatus `protobuf:"varint,13,opt,name=validator_status,json=validatorStatus,proto3,enum=cli_pb.DashboardResponse_ValidatorStatus" json:"validator_status,omitempty"` +} + +func (x *DashboardResponse) Reset() { + *x = DashboardResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DashboardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DashboardResponse) ProtoMessage() {} + +func (x *DashboardResponse) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DashboardResponse.ProtoReflect.Descriptor instead. +func (*DashboardResponse) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{5} +} + +func (x *DashboardResponse) GetLatestHeight() int64 { + if x != nil { + return x.LatestHeight + } + return 0 +} + +func (x *DashboardResponse) GetTimestamp() *timestamp.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *DashboardResponse) GetDuration() int64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *DashboardResponse) GetMemoryUsage() uint64 { + if x != nil { + return x.MemoryUsage + } + return 0 +} + +func (x *DashboardResponse) GetValidatorPubKey() string { + if x != nil { + return x.ValidatorPubKey + } + return "" +} + +func (x *DashboardResponse) GetMaxPeerHeight() int64 { + if x != nil { + return x.MaxPeerHeight + } + return 0 +} + +func (x *DashboardResponse) GetPeersCount() int32 { + if x != nil { + return x.PeersCount + } + return 0 +} + +func (x *DashboardResponse) GetAvgBlockProcessingTime() int64 { + if x != nil { + return x.AvgBlockProcessingTime + } + return 0 +} + +func (x *DashboardResponse) GetTimePerBlock() int64 { + if x != nil { + return x.TimePerBlock + } + return 0 +} + +func (x *DashboardResponse) GetMissedBlocks() string { + if x != nil { + return x.MissedBlocks + } + return "" +} + +func (x *DashboardResponse) GetVotingPower() int64 { + if x != nil { + return x.VotingPower + } + return 0 +} + +func (x *DashboardResponse) GetStake() string { + if x != nil { + return x.Stake + } + return "" +} + +func (x *DashboardResponse) GetValidatorStatus() DashboardResponse_ValidatorStatus { + if x != nil { + return x.ValidatorStatus + } + return DashboardResponse_Validating +} + +type PruneBlocksResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Current int64 `protobuf:"varint,2,opt,name=current,proto3" json:"current,omitempty"` +} + +func (x *PruneBlocksResponse) Reset() { + *x = PruneBlocksResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PruneBlocksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PruneBlocksResponse) ProtoMessage() {} + +func (x *PruneBlocksResponse) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PruneBlocksResponse.ProtoReflect.Descriptor instead. +func (*PruneBlocksResponse) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{6} +} + +func (x *PruneBlocksResponse) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *PruneBlocksResponse) GetCurrent() int64 { + if x != nil { + return x.Current + } + return 0 +} + +type NodeInfo_ProtocolVersion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + P2P uint64 `protobuf:"varint,3,opt,name=p2p,proto3" json:"p2p,omitempty"` + Block uint64 `protobuf:"varint,1,opt,name=block,proto3" json:"block,omitempty"` + App uint64 `protobuf:"varint,2,opt,name=app,proto3" json:"app,omitempty"` +} + +func (x *NodeInfo_ProtocolVersion) Reset() { + *x = NodeInfo_ProtocolVersion{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeInfo_ProtocolVersion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeInfo_ProtocolVersion) ProtoMessage() {} + +func (x *NodeInfo_ProtocolVersion) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeInfo_ProtocolVersion.ProtoReflect.Descriptor instead. +func (*NodeInfo_ProtocolVersion) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *NodeInfo_ProtocolVersion) GetP2P() uint64 { + if x != nil { + return x.P2P + } + return 0 +} + +func (x *NodeInfo_ProtocolVersion) GetBlock() uint64 { + if x != nil { + return x.Block + } + return 0 +} + +func (x *NodeInfo_ProtocolVersion) GetApp() uint64 { + if x != nil { + return x.App + } + return 0 +} + +type NodeInfo_Other struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TxIndex string `protobuf:"bytes,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` + RpcAddress string `protobuf:"bytes,1,opt,name=rpc_address,json=rpcAddress,proto3" json:"rpc_address,omitempty"` +} + +func (x *NodeInfo_Other) Reset() { + *x = NodeInfo_Other{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeInfo_Other) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeInfo_Other) ProtoMessage() {} + +func (x *NodeInfo_Other) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeInfo_Other.ProtoReflect.Descriptor instead. +func (*NodeInfo_Other) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *NodeInfo_Other) GetTxIndex() string { + if x != nil { + return x.TxIndex + } + return "" +} + +func (x *NodeInfo_Other) GetRpcAddress() string { + if x != nil { + return x.RpcAddress + } + return "" +} + +type NetInfoResponse_Peer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + LatestBlockHeight int64 `protobuf:"varint,5,opt,name=latest_block_height,json=latestBlockHeight,proto3" json:"latest_block_height,omitempty"` + NodeInfo *NodeInfo `protobuf:"bytes,4,opt,name=node_info,json=nodeInfo,proto3" json:"node_info,omitempty"` + IsOutbound bool `protobuf:"varint,1,opt,name=is_outbound,json=isOutbound,proto3" json:"is_outbound,omitempty"` + ConnectionStatus *NetInfoResponse_Peer_ConnectionStatus `protobuf:"bytes,2,opt,name=connection_status,json=connectionStatus,proto3" json:"connection_status,omitempty"` + RemoteIp string `protobuf:"bytes,3,opt,name=remote_ip,json=remoteIp,proto3" json:"remote_ip,omitempty"` +} + +func (x *NetInfoResponse_Peer) Reset() { + *x = NetInfoResponse_Peer{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NetInfoResponse_Peer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetInfoResponse_Peer) ProtoMessage() {} + +func (x *NetInfoResponse_Peer) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetInfoResponse_Peer.ProtoReflect.Descriptor instead. +func (*NetInfoResponse_Peer) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *NetInfoResponse_Peer) GetLatestBlockHeight() int64 { + if x != nil { + return x.LatestBlockHeight + } + return 0 +} + +func (x *NetInfoResponse_Peer) GetNodeInfo() *NodeInfo { + if x != nil { + return x.NodeInfo + } + return nil +} + +func (x *NetInfoResponse_Peer) GetIsOutbound() bool { + if x != nil { + return x.IsOutbound + } + return false +} + +func (x *NetInfoResponse_Peer) GetConnectionStatus() *NetInfoResponse_Peer_ConnectionStatus { + if x != nil { + return x.ConnectionStatus + } + return nil +} + +func (x *NetInfoResponse_Peer) GetRemoteIp() string { + if x != nil { + return x.RemoteIp + } + return "" +} + +type NetInfoResponse_Peer_ConnectionStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Duration int64 `protobuf:"varint,4,opt,name=duration,proto3" json:"duration,omitempty"` + SendMonitor *NetInfoResponse_Peer_ConnectionStatus_Monitor `protobuf:"bytes,1,opt,name=SendMonitor,proto3" json:"SendMonitor,omitempty"` + RecvMonitor *NetInfoResponse_Peer_ConnectionStatus_Monitor `protobuf:"bytes,2,opt,name=RecvMonitor,proto3" json:"RecvMonitor,omitempty"` + Channels []*NetInfoResponse_Peer_ConnectionStatus_Channel `protobuf:"bytes,3,rep,name=channels,proto3" json:"channels,omitempty"` +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) Reset() { + *x = NetInfoResponse_Peer_ConnectionStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetInfoResponse_Peer_ConnectionStatus) ProtoMessage() {} + +func (x *NetInfoResponse_Peer_ConnectionStatus) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetInfoResponse_Peer_ConnectionStatus.ProtoReflect.Descriptor instead. +func (*NetInfoResponse_Peer_ConnectionStatus) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{1, 0, 0} +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) GetDuration() int64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) GetSendMonitor() *NetInfoResponse_Peer_ConnectionStatus_Monitor { + if x != nil { + return x.SendMonitor + } + return nil +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) GetRecvMonitor() *NetInfoResponse_Peer_ConnectionStatus_Monitor { + if x != nil { + return x.RecvMonitor + } + return nil +} + +func (x *NetInfoResponse_Peer_ConnectionStatus) GetChannels() []*NetInfoResponse_Peer_ConnectionStatus_Channel { + if x != nil { + return x.Channels + } + return nil +} + +type NetInfoResponse_Peer_ConnectionStatus_Monitor struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Active bool `protobuf:"varint,13,opt,name=active,proto3" json:"active,omitempty"` + Start string `protobuf:"bytes,1,opt,name=start,proto3" json:"start,omitempty"` + Duration int64 `protobuf:"varint,2,opt,name=duration,proto3" json:"duration,omitempty"` + Idle int64 `protobuf:"varint,3,opt,name=idle,proto3" json:"idle,omitempty"` + Bytes int64 `protobuf:"varint,4,opt,name=bytes,proto3" json:"bytes,omitempty"` + Samples int64 `protobuf:"varint,5,opt,name=samples,proto3" json:"samples,omitempty"` + InstRate int64 `protobuf:"varint,6,opt,name=inst_rate,json=instRate,proto3" json:"inst_rate,omitempty"` + CurRate int64 `protobuf:"varint,7,opt,name=cur_rate,json=curRate,proto3" json:"cur_rate,omitempty"` + AvgRate int64 `protobuf:"varint,8,opt,name=avg_rate,json=avgRate,proto3" json:"avg_rate,omitempty"` + PeakRate int64 `protobuf:"varint,9,opt,name=peak_rate,json=peakRate,proto3" json:"peak_rate,omitempty"` + BytesRem int64 `protobuf:"varint,10,opt,name=bytes_rem,json=bytesRem,proto3" json:"bytes_rem,omitempty"` + TimeRem int64 `protobuf:"varint,11,opt,name=time_rem,json=timeRem,proto3" json:"time_rem,omitempty"` + Progress uint32 `protobuf:"varint,12,opt,name=progress,proto3" json:"progress,omitempty"` +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) Reset() { + *x = NetInfoResponse_Peer_ConnectionStatus_Monitor{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetInfoResponse_Peer_ConnectionStatus_Monitor) ProtoMessage() {} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetInfoResponse_Peer_ConnectionStatus_Monitor.ProtoReflect.Descriptor instead. +func (*NetInfoResponse_Peer_ConnectionStatus_Monitor) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{1, 0, 0, 0} +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetActive() bool { + if x != nil { + return x.Active + } + return false +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetStart() string { + if x != nil { + return x.Start + } + return "" +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetDuration() int64 { + if x != nil { + return x.Duration + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetIdle() int64 { + if x != nil { + return x.Idle + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetBytes() int64 { + if x != nil { + return x.Bytes + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetSamples() int64 { + if x != nil { + return x.Samples + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetInstRate() int64 { + if x != nil { + return x.InstRate + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetCurRate() int64 { + if x != nil { + return x.CurRate + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetAvgRate() int64 { + if x != nil { + return x.AvgRate + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetPeakRate() int64 { + if x != nil { + return x.PeakRate + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetBytesRem() int64 { + if x != nil { + return x.BytesRem + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetTimeRem() int64 { + if x != nil { + return x.TimeRem + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetProgress() uint32 { + if x != nil { + return x.Progress + } + return 0 +} + +type NetInfoResponse_Peer_ConnectionStatus_Channel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id int32 `protobuf:"varint,5,opt,name=id,proto3" json:"id,omitempty"` + SendQueueCapacity int64 `protobuf:"varint,1,opt,name=send_queue_capacity,json=sendQueueCapacity,proto3" json:"send_queue_capacity,omitempty"` + SendQueueSize int64 `protobuf:"varint,2,opt,name=send_queue_size,json=sendQueueSize,proto3" json:"send_queue_size,omitempty"` + Priority int64 `protobuf:"varint,3,opt,name=priority,proto3" json:"priority,omitempty"` + RecentlySent int64 `protobuf:"varint,4,opt,name=recently_sent,json=recentlySent,proto3" json:"recently_sent,omitempty"` +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) Reset() { + *x = NetInfoResponse_Peer_ConnectionStatus_Channel{} + if protoimpl.UnsafeEnabled { + mi := &file_manager_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NetInfoResponse_Peer_ConnectionStatus_Channel) ProtoMessage() {} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) ProtoReflect() protoreflect.Message { + mi := &file_manager_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NetInfoResponse_Peer_ConnectionStatus_Channel.ProtoReflect.Descriptor instead. +func (*NetInfoResponse_Peer_ConnectionStatus_Channel) Descriptor() ([]byte, []int) { + return file_manager_proto_rawDescGZIP(), []int{1, 0, 0, 1} +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) GetId() int32 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) GetSendQueueCapacity() int64 { + if x != nil { + return x.SendQueueCapacity + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) GetSendQueueSize() int64 { + if x != nil { + return x.SendQueueSize + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) GetPriority() int64 { + if x != nil { + return x.Priority + } + return 0 +} + +func (x *NetInfoResponse_Peer_ConnectionStatus_Channel) GetRecentlySent() int64 { + if x != nil { + return x.RecentlySent + } + return 0 +} + +var File_manager_proto protoreflect.FileDescriptor + +var file_manager_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x06, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x03, 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x4b, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, + 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x1f, 0x0a, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, + 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x6e, 0x69, 0x6b, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x6f, 0x6e, 0x69, 0x6b, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x05, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x63, 0x6c, 0x69, 0x5f, + 0x70, 0x62, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4f, 0x74, 0x68, 0x65, + 0x72, 0x52, 0x05, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x1a, 0x4b, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x70, + 0x32, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x70, 0x32, 0x70, 0x12, 0x14, 0x0a, + 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x70, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x03, 0x61, 0x70, 0x70, 0x1a, 0x43, 0x0a, 0x05, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x12, 0x19, + 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x74, 0x78, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x70, 0x63, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x72, 0x70, 0x63, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xed, 0x09, 0x0a, 0x0f, 0x4e, + 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x0a, 0x09, + 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0a, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x32, 0x0a, 0x05, 0x70, + 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6c, 0x69, + 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x05, 0x70, 0x65, 0x65, 0x72, 0x73, 0x1a, + 0xc8, 0x08, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2d, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x63, 0x6c, + 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x6e, + 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x6f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, + 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x5a, 0x0a, 0x11, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x65, 0x74, + 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, + 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x69, + 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x49, + 0x70, 0x1a, 0xc6, 0x06, 0x0a, 0x10, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x57, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, + 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x0b, + 0x53, 0x65, 0x6e, 0x64, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x12, 0x57, 0x0a, 0x0b, 0x52, + 0x65, 0x63, 0x76, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x35, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x2e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, + 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x52, 0x0b, 0x52, 0x65, 0x63, 0x76, 0x4d, 0x6f, 0x6e, + 0x69, 0x74, 0x6f, 0x72, 0x12, 0x51, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, + 0x4e, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x50, 0x65, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x08, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0xdb, 0x02, 0x0a, 0x07, 0x4d, 0x6f, 0x6e, 0x69, + 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x69, 0x64, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x69, 0x64, 0x6c, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x73, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x75, 0x72, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x63, 0x75, 0x72, 0x52, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x76, 0x67, + 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x61, 0x76, 0x67, + 0x52, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x65, 0x61, 0x6b, 0x5f, 0x72, 0x61, 0x74, + 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x65, 0x61, 0x6b, 0x52, 0x61, 0x74, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x6d, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x6d, 0x12, 0x19, + 0x0a, 0x08, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x6d, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x1a, 0xb2, 0x01, 0x0a, 0x07, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, + 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, + 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x11, + 0x73, 0x65, 0x6e, 0x64, 0x51, 0x75, 0x65, 0x75, 0x65, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, + 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x65, 0x6e, 0x64, + 0x51, 0x75, 0x65, 0x75, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x72, 0x69, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x6c, + 0x79, 0x5f, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x72, 0x65, + 0x63, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x53, 0x65, 0x6e, 0x74, 0x22, 0xdd, 0x02, 0x0a, 0x0e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x6c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x26, 0x0a, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x70, + 0x70, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x41, 0x70, 0x70, 0x48, 0x61, 0x73, 0x68, 0x12, 0x2e, 0x0a, 0x13, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6b, 0x65, 0x65, 0x70, 0x5f, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x6b, 0x65, 0x65, 0x70, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x70, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, + 0x55, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x22, 0x52, 0x0a, 0x12, 0x50, 0x72, + 0x75, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x6f, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x74, 0x6f, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x4b, + 0x0a, 0x0f, 0x44, 0x65, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x70, + 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x05, 0x0a, 0x11, + 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, + 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x2a, 0x0a, 0x11, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x70, 0x75, 0x62, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, + 0x64, 0x61, 0x74, 0x6f, 0x72, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x26, 0x0a, 0x0f, 0x6d, + 0x61, 0x78, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x50, 0x65, 0x65, 0x72, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x65, 0x65, 0x72, 0x73, 0x5f, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x65, 0x65, 0x72, 0x73, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x39, 0x0a, 0x19, 0x61, 0x76, 0x67, 0x5f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x61, 0x76, 0x67, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x24, 0x0a, 0x0e, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x50, 0x65, 0x72, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x69, 0x73, 0x73, 0x65, 0x64, 0x5f, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6d, 0x69, + 0x73, 0x73, 0x65, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x6f, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0b, 0x76, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x14, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, + 0x61, 0x6b, 0x65, 0x12, 0x54, 0x0a, 0x10, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, + 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0f, 0x56, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0e, 0x0a, 0x0a, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, + 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x72, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x4f, 0x66, 0x66, 0x6c, 0x69, 0x6e, 0x65, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x6f, 0x74, + 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x65, 0x64, 0x10, 0x03, 0x22, 0x45, 0x0a, 0x13, 0x50, 0x72, + 0x75, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x32, 0xcf, 0x02, 0x0a, 0x0e, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, + 0x0a, 0x07, 0x4e, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x17, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x4e, 0x65, 0x74, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x48, 0x0a, 0x0b, 0x50, 0x72, + 0x75, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x12, 0x1a, 0x2e, 0x63, 0x6c, 0x69, 0x5f, + 0x70, 0x62, 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x50, + 0x72, 0x75, 0x6e, 0x65, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x30, 0x01, 0x12, 0x3b, 0x0a, 0x08, 0x44, 0x65, 0x61, 0x6c, 0x50, 0x65, 0x65, 0x72, + 0x12, 0x17, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x50, 0x65, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x40, 0x0a, 0x09, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x2e, + 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x30, 0x01, 0x42, 0x0a, 0x5a, 0x08, 0x2e, 0x3b, 0x63, 0x6c, 0x69, 0x5f, 0x70, 0x62, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_manager_proto_rawDescOnce sync.Once + file_manager_proto_rawDescData = file_manager_proto_rawDesc +) + +func file_manager_proto_rawDescGZIP() []byte { + file_manager_proto_rawDescOnce.Do(func() { + file_manager_proto_rawDescData = protoimpl.X.CompressGZIP(file_manager_proto_rawDescData) + }) + return file_manager_proto_rawDescData +} + +var file_manager_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_manager_proto_goTypes = []interface{}{ + (DashboardResponse_ValidatorStatus)(0), // 0: cli_pb.DashboardResponse.ValidatorStatus + (*NodeInfo)(nil), // 1: cli_pb.NodeInfo + (*NetInfoResponse)(nil), // 2: cli_pb.NetInfoResponse + (*StatusResponse)(nil), // 3: cli_pb.StatusResponse + (*PruneBlocksRequest)(nil), // 4: cli_pb.PruneBlocksRequest + (*DealPeerRequest)(nil), // 5: cli_pb.DealPeerRequest + (*DashboardResponse)(nil), // 6: cli_pb.DashboardResponse + (*PruneBlocksResponse)(nil), // 7: cli_pb.PruneBlocksResponse + (*NodeInfo_ProtocolVersion)(nil), // 8: cli_pb.NodeInfo.ProtocolVersion + (*NodeInfo_Other)(nil), // 9: cli_pb.NodeInfo.Other + (*NetInfoResponse_Peer)(nil), // 10: cli_pb.NetInfoResponse.Peer + (*NetInfoResponse_Peer_ConnectionStatus)(nil), // 11: cli_pb.NetInfoResponse.Peer.ConnectionStatus + (*NetInfoResponse_Peer_ConnectionStatus_Monitor)(nil), // 12: cli_pb.NetInfoResponse.Peer.ConnectionStatus.Monitor + (*NetInfoResponse_Peer_ConnectionStatus_Channel)(nil), // 13: cli_pb.NetInfoResponse.Peer.ConnectionStatus.Channel + (*timestamp.Timestamp)(nil), // 14: google.protobuf.Timestamp + (*empty.Empty)(nil), // 15: google.protobuf.Empty +} +var file_manager_proto_depIdxs = []int32{ + 8, // 0: cli_pb.NodeInfo.protocol_version:type_name -> cli_pb.NodeInfo.ProtocolVersion + 9, // 1: cli_pb.NodeInfo.other:type_name -> cli_pb.NodeInfo.Other + 10, // 2: cli_pb.NetInfoResponse.peers:type_name -> cli_pb.NetInfoResponse.Peer + 14, // 3: cli_pb.DashboardResponse.timestamp:type_name -> google.protobuf.Timestamp + 0, // 4: cli_pb.DashboardResponse.validator_status:type_name -> cli_pb.DashboardResponse.ValidatorStatus + 1, // 5: cli_pb.NetInfoResponse.Peer.node_info:type_name -> cli_pb.NodeInfo + 11, // 6: cli_pb.NetInfoResponse.Peer.connection_status:type_name -> cli_pb.NetInfoResponse.Peer.ConnectionStatus + 12, // 7: cli_pb.NetInfoResponse.Peer.ConnectionStatus.SendMonitor:type_name -> cli_pb.NetInfoResponse.Peer.ConnectionStatus.Monitor + 12, // 8: cli_pb.NetInfoResponse.Peer.ConnectionStatus.RecvMonitor:type_name -> cli_pb.NetInfoResponse.Peer.ConnectionStatus.Monitor + 13, // 9: cli_pb.NetInfoResponse.Peer.ConnectionStatus.channels:type_name -> cli_pb.NetInfoResponse.Peer.ConnectionStatus.Channel + 15, // 10: cli_pb.ManagerService.Status:input_type -> google.protobuf.Empty + 15, // 11: cli_pb.ManagerService.NetInfo:input_type -> google.protobuf.Empty + 4, // 12: cli_pb.ManagerService.PruneBlocks:input_type -> cli_pb.PruneBlocksRequest + 5, // 13: cli_pb.ManagerService.DealPeer:input_type -> cli_pb.DealPeerRequest + 15, // 14: cli_pb.ManagerService.Dashboard:input_type -> google.protobuf.Empty + 3, // 15: cli_pb.ManagerService.Status:output_type -> cli_pb.StatusResponse + 2, // 16: cli_pb.ManagerService.NetInfo:output_type -> cli_pb.NetInfoResponse + 7, // 17: cli_pb.ManagerService.PruneBlocks:output_type -> cli_pb.PruneBlocksResponse + 15, // 18: cli_pb.ManagerService.DealPeer:output_type -> google.protobuf.Empty + 6, // 19: cli_pb.ManagerService.Dashboard:output_type -> cli_pb.DashboardResponse + 15, // [15:20] is the sub-list for method output_type + 10, // [10:15] is the sub-list for method input_type + 10, // [10:10] is the sub-list for extension type_name + 10, // [10:10] is the sub-list for extension extendee + 0, // [0:10] is the sub-list for field type_name +} + +func init() { file_manager_proto_init() } +func file_manager_proto_init() { + if File_manager_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_manager_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetInfoResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PruneBlocksRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DealPeerRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DashboardResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PruneBlocksResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfo_ProtocolVersion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfo_Other); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetInfoResponse_Peer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetInfoResponse_Peer_ConnectionStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetInfoResponse_Peer_ConnectionStatus_Monitor); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_manager_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NetInfoResponse_Peer_ConnectionStatus_Channel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_manager_proto_rawDesc, + NumEnums: 1, + NumMessages: 13, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_manager_proto_goTypes, + DependencyIndexes: file_manager_proto_depIdxs, + EnumInfos: file_manager_proto_enumTypes, + MessageInfos: file_manager_proto_msgTypes, + }.Build() + File_manager_proto = out.File + file_manager_proto_rawDesc = nil + file_manager_proto_goTypes = nil + file_manager_proto_depIdxs = nil +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConnInterface + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion6 + +// ManagerServiceClient is the client API for ManagerService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ManagerServiceClient interface { + Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error) + NetInfo(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*NetInfoResponse, error) + PruneBlocks(ctx context.Context, in *PruneBlocksRequest, opts ...grpc.CallOption) (ManagerService_PruneBlocksClient, error) + DealPeer(ctx context.Context, in *DealPeerRequest, opts ...grpc.CallOption) (*empty.Empty, error) + Dashboard(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (ManagerService_DashboardClient, error) +} + +type managerServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewManagerServiceClient(cc grpc.ClientConnInterface) ManagerServiceClient { + return &managerServiceClient{cc} +} + +func (c *managerServiceClient) Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := c.cc.Invoke(ctx, "/cli_pb.ManagerService/Status", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *managerServiceClient) NetInfo(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*NetInfoResponse, error) { + out := new(NetInfoResponse) + err := c.cc.Invoke(ctx, "/cli_pb.ManagerService/NetInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *managerServiceClient) PruneBlocks(ctx context.Context, in *PruneBlocksRequest, opts ...grpc.CallOption) (ManagerService_PruneBlocksClient, error) { + stream, err := c.cc.NewStream(ctx, &_ManagerService_serviceDesc.Streams[0], "/cli_pb.ManagerService/PruneBlocks", opts...) + if err != nil { + return nil, err + } + x := &managerServicePruneBlocksClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ManagerService_PruneBlocksClient interface { + Recv() (*PruneBlocksResponse, error) + grpc.ClientStream +} + +type managerServicePruneBlocksClient struct { + grpc.ClientStream +} + +func (x *managerServicePruneBlocksClient) Recv() (*PruneBlocksResponse, error) { + m := new(PruneBlocksResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *managerServiceClient) DealPeer(ctx context.Context, in *DealPeerRequest, opts ...grpc.CallOption) (*empty.Empty, error) { + out := new(empty.Empty) + err := c.cc.Invoke(ctx, "/cli_pb.ManagerService/DealPeer", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *managerServiceClient) Dashboard(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (ManagerService_DashboardClient, error) { + stream, err := c.cc.NewStream(ctx, &_ManagerService_serviceDesc.Streams[1], "/cli_pb.ManagerService/Dashboard", opts...) + if err != nil { + return nil, err + } + x := &managerServiceDashboardClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ManagerService_DashboardClient interface { + Recv() (*DashboardResponse, error) + grpc.ClientStream +} + +type managerServiceDashboardClient struct { + grpc.ClientStream +} + +func (x *managerServiceDashboardClient) Recv() (*DashboardResponse, error) { + m := new(DashboardResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// ManagerServiceServer is the server API for ManagerService service. +type ManagerServiceServer interface { + Status(context.Context, *empty.Empty) (*StatusResponse, error) + NetInfo(context.Context, *empty.Empty) (*NetInfoResponse, error) + PruneBlocks(*PruneBlocksRequest, ManagerService_PruneBlocksServer) error + DealPeer(context.Context, *DealPeerRequest) (*empty.Empty, error) + Dashboard(*empty.Empty, ManagerService_DashboardServer) error +} + +// UnimplementedManagerServiceServer can be embedded to have forward compatible implementations. +type UnimplementedManagerServiceServer struct { +} + +func (*UnimplementedManagerServiceServer) Status(context.Context, *empty.Empty) (*StatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") +} +func (*UnimplementedManagerServiceServer) NetInfo(context.Context, *empty.Empty) (*NetInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NetInfo not implemented") +} +func (*UnimplementedManagerServiceServer) PruneBlocks(*PruneBlocksRequest, ManagerService_PruneBlocksServer) error { + return status.Errorf(codes.Unimplemented, "method PruneBlocks not implemented") +} +func (*UnimplementedManagerServiceServer) DealPeer(context.Context, *DealPeerRequest) (*empty.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DealPeer not implemented") +} +func (*UnimplementedManagerServiceServer) Dashboard(*empty.Empty, ManagerService_DashboardServer) error { + return status.Errorf(codes.Unimplemented, "method Dashboard not implemented") +} + +func RegisterManagerServiceServer(s *grpc.Server, srv ManagerServiceServer) { + s.RegisterService(&_ManagerService_serviceDesc, srv) +} + +func _ManagerService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ManagerServiceServer).Status(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cli_pb.ManagerService/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ManagerServiceServer).Status(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ManagerService_NetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(empty.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ManagerServiceServer).NetInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cli_pb.ManagerService/NetInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ManagerServiceServer).NetInfo(ctx, req.(*empty.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ManagerService_PruneBlocks_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(PruneBlocksRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ManagerServiceServer).PruneBlocks(m, &managerServicePruneBlocksServer{stream}) +} + +type ManagerService_PruneBlocksServer interface { + Send(*PruneBlocksResponse) error + grpc.ServerStream +} + +type managerServicePruneBlocksServer struct { + grpc.ServerStream +} + +func (x *managerServicePruneBlocksServer) Send(m *PruneBlocksResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _ManagerService_DealPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DealPeerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ManagerServiceServer).DealPeer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cli_pb.ManagerService/DealPeer", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ManagerServiceServer).DealPeer(ctx, req.(*DealPeerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ManagerService_Dashboard_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(empty.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ManagerServiceServer).Dashboard(m, &managerServiceDashboardServer{stream}) +} + +type ManagerService_DashboardServer interface { + Send(*DashboardResponse) error + grpc.ServerStream +} + +type managerServiceDashboardServer struct { + grpc.ServerStream +} + +func (x *managerServiceDashboardServer) Send(m *DashboardResponse) error { + return x.ServerStream.SendMsg(m) +} + +var _ManagerService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cli_pb.ManagerService", + HandlerType: (*ManagerServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Status", + Handler: _ManagerService_Status_Handler, + }, + { + MethodName: "NetInfo", + Handler: _ManagerService_NetInfo_Handler, + }, + { + MethodName: "DealPeer", + Handler: _ManagerService_DealPeer_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "PruneBlocks", + Handler: _ManagerService_PruneBlocks_Handler, + ServerStreams: true, + }, + { + StreamName: "Dashboard", + Handler: _ManagerService_Dashboard_Handler, + ServerStreams: true, + }, + }, + Metadata: "manager.proto", +} diff --git a/cli/pb/manager.proto b/cli/cli_pb/manager.proto similarity index 57% rename from cli/pb/manager.proto rename to cli/cli_pb/manager.proto index e60f45852..1a9187a71 100644 --- a/cli/pb/manager.proto +++ b/cli/cli_pb/manager.proto @@ -1,10 +1,10 @@ syntax = "proto3"; -// protoc --go_out=plugins=grpc:. *.proto -package pb; - +package cli_pb; +option go_package = ".;cli_pb"; import "google/protobuf/empty.proto"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; message NodeInfo { @@ -33,46 +33,42 @@ message NodeInfo { message NetInfoResponse { bool listening = 4; repeated string listeners = 1; - int64 n_peers = 2; + int64 count_peers = 2; message Peer { + int64 latest_block_height = 5; NodeInfo node_info = 4; bool is_outbound = 1; message ConnectionStatus { - int64 Duration = 4; - + int64 duration = 4; message Monitor { - bool Active = 13; - string Start = 1; - int64 Duration = 2; - int64 Idle = 3; - int64 Bytes = 4; - int64 Samples = 5; - int64 InstRate = 6; - int64 CurRate = 7; - int64 AvgRate = 8; - int64 PeakRate = 9; - int64 BytesRem = 10; - int64 TimeRem = 11; - uint32 Progress = 12; + bool active = 13; + string start = 1; + int64 duration = 2; + int64 idle = 3; + int64 bytes = 4; + int64 samples = 5; + int64 inst_rate = 6; + int64 cur_rate = 7; + int64 avg_rate = 8; + int64 peak_rate = 9; + int64 bytes_rem = 10; + int64 time_rem = 11; + uint32 progress = 12; } - Monitor SendMonitor = 1; - Monitor RecvMonitor = 2; - message Channel { - int32 ID = 5; - int64 SendQueueCapacity = 1; - int64 SendQueueSize = 2; - int64 Priority = 3; - int64 RecentlySent = 4; + int32 id = 5; + int64 send_queue_capacity = 1; + int64 send_queue_size = 2; + int64 priority = 3; + int64 recently_sent = 4; } - - repeated Channel Channels = 3; + repeated Channel channels = 3; } ConnectionStatus connection_status = 2; @@ -83,43 +79,15 @@ message NetInfoResponse { } message StatusResponse { - string version = 7; + string version = 8; string latest_block_hash = 1; string latest_app_hash = 2; - int64 latest_block_height = 3; + string latest_block_height = 3; string latest_block_time = 4; - int64 keep_last_states = 5; - - message TmStatus { - - NodeInfo node_info = 3; - - message SyncInfo { - string latest_block_hash = 5; - string latest_app_hash = 1; - int64 latest_block_height = 2; - string latest_block_time = 3; - bool catching_up = 4; - } - - SyncInfo sync_info = 1; - - message ValidatorInfo { - string address = 3; - - message PubKey { - string type = 2; - string value = 1; - } - - PubKey pub_key = 1; - int64 voting_power = 2; - } - - ValidatorInfo validator_info = 2; - } - - TmStatus tm_status = 6; + string keep_last_states = 5; + bool catching_up = 6; + string public_key = 7; + string node_id = 9; } message PruneBlocksRequest { @@ -154,10 +122,15 @@ message DashboardResponse { ValidatorStatus validator_status = 13; } +message PruneBlocksResponse { + int64 total = 1; + int64 current = 2; +} + service ManagerService { rpc Status (google.protobuf.Empty) returns (StatusResponse); rpc NetInfo (google.protobuf.Empty) returns (NetInfoResponse); - rpc PruneBlocks (PruneBlocksRequest) returns (google.protobuf.Empty); + rpc PruneBlocks (PruneBlocksRequest) returns (stream PruneBlocksResponse); rpc DealPeer (DealPeerRequest) returns (google.protobuf.Empty); rpc Dashboard (google.protobuf.Empty) returns (stream DashboardResponse); } diff --git a/cli/pb/manager.pb.go b/cli/pb/manager.pb.go deleted file mode 100644 index fa0c750f4..000000000 --- a/cli/pb/manager.pb.go +++ /dev/null @@ -1,1583 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// source: manager.proto - -// protoc --go_out=plugins=grpc:. *.proto - -package pb - -import ( - context "context" - fmt "fmt" - proto "github.com/golang/protobuf/proto" - empty "github.com/golang/protobuf/ptypes/empty" - timestamp "github.com/golang/protobuf/ptypes/timestamp" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - math "math" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package - -type DashboardResponse_ValidatorStatus int32 - -const ( - DashboardResponse_Validating DashboardResponse_ValidatorStatus = 0 - DashboardResponse_Challenger DashboardResponse_ValidatorStatus = 1 - DashboardResponse_Offline DashboardResponse_ValidatorStatus = 2 - DashboardResponse_NotDeclared DashboardResponse_ValidatorStatus = 3 -) - -var DashboardResponse_ValidatorStatus_name = map[int32]string{ - 0: "Validating", - 1: "Challenger", - 2: "Offline", - 3: "NotDeclared", -} - -var DashboardResponse_ValidatorStatus_value = map[string]int32{ - "Validating": 0, - "Challenger": 1, - "Offline": 2, - "NotDeclared": 3, -} - -func (x DashboardResponse_ValidatorStatus) String() string { - return proto.EnumName(DashboardResponse_ValidatorStatus_name, int32(x)) -} - -func (DashboardResponse_ValidatorStatus) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{5, 0} -} - -type NodeInfo struct { - ProtocolVersion *NodeInfo_ProtocolVersion `protobuf:"bytes,8,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - ListenAddr string `protobuf:"bytes,2,opt,name=listen_addr,json=listenAddr,proto3" json:"listen_addr,omitempty"` - Network string `protobuf:"bytes,3,opt,name=network,proto3" json:"network,omitempty"` - Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` - Channels string `protobuf:"bytes,5,opt,name=channels,proto3" json:"channels,omitempty"` - Moniker string `protobuf:"bytes,6,opt,name=moniker,proto3" json:"moniker,omitempty"` - Other *NodeInfo_Other `protobuf:"bytes,7,opt,name=other,proto3" json:"other,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NodeInfo) Reset() { *m = NodeInfo{} } -func (m *NodeInfo) String() string { return proto.CompactTextString(m) } -func (*NodeInfo) ProtoMessage() {} -func (*NodeInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{0} -} - -func (m *NodeInfo) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NodeInfo.Unmarshal(m, b) -} -func (m *NodeInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NodeInfo.Marshal(b, m, deterministic) -} -func (m *NodeInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_NodeInfo.Merge(m, src) -} -func (m *NodeInfo) XXX_Size() int { - return xxx_messageInfo_NodeInfo.Size(m) -} -func (m *NodeInfo) XXX_DiscardUnknown() { - xxx_messageInfo_NodeInfo.DiscardUnknown(m) -} - -var xxx_messageInfo_NodeInfo proto.InternalMessageInfo - -func (m *NodeInfo) GetProtocolVersion() *NodeInfo_ProtocolVersion { - if m != nil { - return m.ProtocolVersion - } - return nil -} - -func (m *NodeInfo) GetId() string { - if m != nil { - return m.Id - } - return "" -} - -func (m *NodeInfo) GetListenAddr() string { - if m != nil { - return m.ListenAddr - } - return "" -} - -func (m *NodeInfo) GetNetwork() string { - if m != nil { - return m.Network - } - return "" -} - -func (m *NodeInfo) GetVersion() string { - if m != nil { - return m.Version - } - return "" -} - -func (m *NodeInfo) GetChannels() string { - if m != nil { - return m.Channels - } - return "" -} - -func (m *NodeInfo) GetMoniker() string { - if m != nil { - return m.Moniker - } - return "" -} - -func (m *NodeInfo) GetOther() *NodeInfo_Other { - if m != nil { - return m.Other - } - return nil -} - -type NodeInfo_ProtocolVersion struct { - P2P uint64 `protobuf:"varint,3,opt,name=p2p,proto3" json:"p2p,omitempty"` - Block uint64 `protobuf:"varint,1,opt,name=block,proto3" json:"block,omitempty"` - App uint64 `protobuf:"varint,2,opt,name=app,proto3" json:"app,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NodeInfo_ProtocolVersion) Reset() { *m = NodeInfo_ProtocolVersion{} } -func (m *NodeInfo_ProtocolVersion) String() string { return proto.CompactTextString(m) } -func (*NodeInfo_ProtocolVersion) ProtoMessage() {} -func (*NodeInfo_ProtocolVersion) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{0, 0} -} - -func (m *NodeInfo_ProtocolVersion) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NodeInfo_ProtocolVersion.Unmarshal(m, b) -} -func (m *NodeInfo_ProtocolVersion) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NodeInfo_ProtocolVersion.Marshal(b, m, deterministic) -} -func (m *NodeInfo_ProtocolVersion) XXX_Merge(src proto.Message) { - xxx_messageInfo_NodeInfo_ProtocolVersion.Merge(m, src) -} -func (m *NodeInfo_ProtocolVersion) XXX_Size() int { - return xxx_messageInfo_NodeInfo_ProtocolVersion.Size(m) -} -func (m *NodeInfo_ProtocolVersion) XXX_DiscardUnknown() { - xxx_messageInfo_NodeInfo_ProtocolVersion.DiscardUnknown(m) -} - -var xxx_messageInfo_NodeInfo_ProtocolVersion proto.InternalMessageInfo - -func (m *NodeInfo_ProtocolVersion) GetP2P() uint64 { - if m != nil { - return m.P2P - } - return 0 -} - -func (m *NodeInfo_ProtocolVersion) GetBlock() uint64 { - if m != nil { - return m.Block - } - return 0 -} - -func (m *NodeInfo_ProtocolVersion) GetApp() uint64 { - if m != nil { - return m.App - } - return 0 -} - -type NodeInfo_Other struct { - TxIndex string `protobuf:"bytes,2,opt,name=tx_index,json=txIndex,proto3" json:"tx_index,omitempty"` - RpcAddress string `protobuf:"bytes,1,opt,name=rpc_address,json=rpcAddress,proto3" json:"rpc_address,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NodeInfo_Other) Reset() { *m = NodeInfo_Other{} } -func (m *NodeInfo_Other) String() string { return proto.CompactTextString(m) } -func (*NodeInfo_Other) ProtoMessage() {} -func (*NodeInfo_Other) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{0, 1} -} - -func (m *NodeInfo_Other) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NodeInfo_Other.Unmarshal(m, b) -} -func (m *NodeInfo_Other) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NodeInfo_Other.Marshal(b, m, deterministic) -} -func (m *NodeInfo_Other) XXX_Merge(src proto.Message) { - xxx_messageInfo_NodeInfo_Other.Merge(m, src) -} -func (m *NodeInfo_Other) XXX_Size() int { - return xxx_messageInfo_NodeInfo_Other.Size(m) -} -func (m *NodeInfo_Other) XXX_DiscardUnknown() { - xxx_messageInfo_NodeInfo_Other.DiscardUnknown(m) -} - -var xxx_messageInfo_NodeInfo_Other proto.InternalMessageInfo - -func (m *NodeInfo_Other) GetTxIndex() string { - if m != nil { - return m.TxIndex - } - return "" -} - -func (m *NodeInfo_Other) GetRpcAddress() string { - if m != nil { - return m.RpcAddress - } - return "" -} - -type NetInfoResponse struct { - Listening bool `protobuf:"varint,4,opt,name=listening,proto3" json:"listening,omitempty"` - Listeners []string `protobuf:"bytes,1,rep,name=listeners,proto3" json:"listeners,omitempty"` - NPeers int64 `protobuf:"varint,2,opt,name=n_peers,json=nPeers,proto3" json:"n_peers,omitempty"` - Peers []*NetInfoResponse_Peer `protobuf:"bytes,3,rep,name=peers,proto3" json:"peers,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NetInfoResponse) Reset() { *m = NetInfoResponse{} } -func (m *NetInfoResponse) String() string { return proto.CompactTextString(m) } -func (*NetInfoResponse) ProtoMessage() {} -func (*NetInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{1} -} - -func (m *NetInfoResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NetInfoResponse.Unmarshal(m, b) -} -func (m *NetInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NetInfoResponse.Marshal(b, m, deterministic) -} -func (m *NetInfoResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_NetInfoResponse.Merge(m, src) -} -func (m *NetInfoResponse) XXX_Size() int { - return xxx_messageInfo_NetInfoResponse.Size(m) -} -func (m *NetInfoResponse) XXX_DiscardUnknown() { - xxx_messageInfo_NetInfoResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_NetInfoResponse proto.InternalMessageInfo - -func (m *NetInfoResponse) GetListening() bool { - if m != nil { - return m.Listening - } - return false -} - -func (m *NetInfoResponse) GetListeners() []string { - if m != nil { - return m.Listeners - } - return nil -} - -func (m *NetInfoResponse) GetNPeers() int64 { - if m != nil { - return m.NPeers - } - return 0 -} - -func (m *NetInfoResponse) GetPeers() []*NetInfoResponse_Peer { - if m != nil { - return m.Peers - } - return nil -} - -type NetInfoResponse_Peer struct { - NodeInfo *NodeInfo `protobuf:"bytes,4,opt,name=node_info,json=nodeInfo,proto3" json:"node_info,omitempty"` - IsOutbound bool `protobuf:"varint,1,opt,name=is_outbound,json=isOutbound,proto3" json:"is_outbound,omitempty"` - ConnectionStatus *NetInfoResponse_Peer_ConnectionStatus `protobuf:"bytes,2,opt,name=connection_status,json=connectionStatus,proto3" json:"connection_status,omitempty"` - RemoteIp string `protobuf:"bytes,3,opt,name=remote_ip,json=remoteIp,proto3" json:"remote_ip,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NetInfoResponse_Peer) Reset() { *m = NetInfoResponse_Peer{} } -func (m *NetInfoResponse_Peer) String() string { return proto.CompactTextString(m) } -func (*NetInfoResponse_Peer) ProtoMessage() {} -func (*NetInfoResponse_Peer) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{1, 0} -} - -func (m *NetInfoResponse_Peer) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NetInfoResponse_Peer.Unmarshal(m, b) -} -func (m *NetInfoResponse_Peer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NetInfoResponse_Peer.Marshal(b, m, deterministic) -} -func (m *NetInfoResponse_Peer) XXX_Merge(src proto.Message) { - xxx_messageInfo_NetInfoResponse_Peer.Merge(m, src) -} -func (m *NetInfoResponse_Peer) XXX_Size() int { - return xxx_messageInfo_NetInfoResponse_Peer.Size(m) -} -func (m *NetInfoResponse_Peer) XXX_DiscardUnknown() { - xxx_messageInfo_NetInfoResponse_Peer.DiscardUnknown(m) -} - -var xxx_messageInfo_NetInfoResponse_Peer proto.InternalMessageInfo - -func (m *NetInfoResponse_Peer) GetNodeInfo() *NodeInfo { - if m != nil { - return m.NodeInfo - } - return nil -} - -func (m *NetInfoResponse_Peer) GetIsOutbound() bool { - if m != nil { - return m.IsOutbound - } - return false -} - -func (m *NetInfoResponse_Peer) GetConnectionStatus() *NetInfoResponse_Peer_ConnectionStatus { - if m != nil { - return m.ConnectionStatus - } - return nil -} - -func (m *NetInfoResponse_Peer) GetRemoteIp() string { - if m != nil { - return m.RemoteIp - } - return "" -} - -type NetInfoResponse_Peer_ConnectionStatus struct { - Duration int64 `protobuf:"varint,4,opt,name=Duration,proto3" json:"Duration,omitempty"` - SendMonitor *NetInfoResponse_Peer_ConnectionStatus_Monitor `protobuf:"bytes,1,opt,name=SendMonitor,proto3" json:"SendMonitor,omitempty"` - RecvMonitor *NetInfoResponse_Peer_ConnectionStatus_Monitor `protobuf:"bytes,2,opt,name=RecvMonitor,proto3" json:"RecvMonitor,omitempty"` - Channels []*NetInfoResponse_Peer_ConnectionStatus_Channel `protobuf:"bytes,3,rep,name=Channels,proto3" json:"Channels,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NetInfoResponse_Peer_ConnectionStatus) Reset() { *m = NetInfoResponse_Peer_ConnectionStatus{} } -func (m *NetInfoResponse_Peer_ConnectionStatus) String() string { return proto.CompactTextString(m) } -func (*NetInfoResponse_Peer_ConnectionStatus) ProtoMessage() {} -func (*NetInfoResponse_Peer_ConnectionStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{1, 0, 0} -} - -func (m *NetInfoResponse_Peer_ConnectionStatus) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus.Unmarshal(m, b) -} -func (m *NetInfoResponse_Peer_ConnectionStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus.Marshal(b, m, deterministic) -} -func (m *NetInfoResponse_Peer_ConnectionStatus) XXX_Merge(src proto.Message) { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus.Merge(m, src) -} -func (m *NetInfoResponse_Peer_ConnectionStatus) XXX_Size() int { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus.Size(m) -} -func (m *NetInfoResponse_Peer_ConnectionStatus) XXX_DiscardUnknown() { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus.DiscardUnknown(m) -} - -var xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus proto.InternalMessageInfo - -func (m *NetInfoResponse_Peer_ConnectionStatus) GetDuration() int64 { - if m != nil { - return m.Duration - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus) GetSendMonitor() *NetInfoResponse_Peer_ConnectionStatus_Monitor { - if m != nil { - return m.SendMonitor - } - return nil -} - -func (m *NetInfoResponse_Peer_ConnectionStatus) GetRecvMonitor() *NetInfoResponse_Peer_ConnectionStatus_Monitor { - if m != nil { - return m.RecvMonitor - } - return nil -} - -func (m *NetInfoResponse_Peer_ConnectionStatus) GetChannels() []*NetInfoResponse_Peer_ConnectionStatus_Channel { - if m != nil { - return m.Channels - } - return nil -} - -type NetInfoResponse_Peer_ConnectionStatus_Monitor struct { - Active bool `protobuf:"varint,13,opt,name=Active,proto3" json:"Active,omitempty"` - Start string `protobuf:"bytes,1,opt,name=Start,proto3" json:"Start,omitempty"` - Duration int64 `protobuf:"varint,2,opt,name=Duration,proto3" json:"Duration,omitempty"` - Idle int64 `protobuf:"varint,3,opt,name=Idle,proto3" json:"Idle,omitempty"` - Bytes int64 `protobuf:"varint,4,opt,name=Bytes,proto3" json:"Bytes,omitempty"` - Samples int64 `protobuf:"varint,5,opt,name=Samples,proto3" json:"Samples,omitempty"` - InstRate int64 `protobuf:"varint,6,opt,name=InstRate,proto3" json:"InstRate,omitempty"` - CurRate int64 `protobuf:"varint,7,opt,name=CurRate,proto3" json:"CurRate,omitempty"` - AvgRate int64 `protobuf:"varint,8,opt,name=AvgRate,proto3" json:"AvgRate,omitempty"` - PeakRate int64 `protobuf:"varint,9,opt,name=PeakRate,proto3" json:"PeakRate,omitempty"` - BytesRem int64 `protobuf:"varint,10,opt,name=BytesRem,proto3" json:"BytesRem,omitempty"` - TimeRem int64 `protobuf:"varint,11,opt,name=TimeRem,proto3" json:"TimeRem,omitempty"` - Progress uint32 `protobuf:"varint,12,opt,name=Progress,proto3" json:"Progress,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) Reset() { - *m = NetInfoResponse_Peer_ConnectionStatus_Monitor{} -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) String() string { - return proto.CompactTextString(m) -} -func (*NetInfoResponse_Peer_ConnectionStatus_Monitor) ProtoMessage() {} -func (*NetInfoResponse_Peer_ConnectionStatus_Monitor) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{1, 0, 0, 0} -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor.Unmarshal(m, b) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor.Marshal(b, m, deterministic) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) XXX_Merge(src proto.Message) { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor.Merge(m, src) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) XXX_Size() int { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor.Size(m) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) XXX_DiscardUnknown() { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor.DiscardUnknown(m) -} - -var xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Monitor proto.InternalMessageInfo - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetActive() bool { - if m != nil { - return m.Active - } - return false -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetStart() string { - if m != nil { - return m.Start - } - return "" -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetDuration() int64 { - if m != nil { - return m.Duration - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetIdle() int64 { - if m != nil { - return m.Idle - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetBytes() int64 { - if m != nil { - return m.Bytes - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetSamples() int64 { - if m != nil { - return m.Samples - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetInstRate() int64 { - if m != nil { - return m.InstRate - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetCurRate() int64 { - if m != nil { - return m.CurRate - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetAvgRate() int64 { - if m != nil { - return m.AvgRate - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetPeakRate() int64 { - if m != nil { - return m.PeakRate - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetBytesRem() int64 { - if m != nil { - return m.BytesRem - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetTimeRem() int64 { - if m != nil { - return m.TimeRem - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Monitor) GetProgress() uint32 { - if m != nil { - return m.Progress - } - return 0 -} - -type NetInfoResponse_Peer_ConnectionStatus_Channel struct { - ID int32 `protobuf:"varint,5,opt,name=ID,proto3" json:"ID,omitempty"` - SendQueueCapacity int64 `protobuf:"varint,1,opt,name=SendQueueCapacity,proto3" json:"SendQueueCapacity,omitempty"` - SendQueueSize int64 `protobuf:"varint,2,opt,name=SendQueueSize,proto3" json:"SendQueueSize,omitempty"` - Priority int64 `protobuf:"varint,3,opt,name=Priority,proto3" json:"Priority,omitempty"` - RecentlySent int64 `protobuf:"varint,4,opt,name=RecentlySent,proto3" json:"RecentlySent,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) Reset() { - *m = NetInfoResponse_Peer_ConnectionStatus_Channel{} -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) String() string { - return proto.CompactTextString(m) -} -func (*NetInfoResponse_Peer_ConnectionStatus_Channel) ProtoMessage() {} -func (*NetInfoResponse_Peer_ConnectionStatus_Channel) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{1, 0, 0, 1} -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel.Unmarshal(m, b) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel.Marshal(b, m, deterministic) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) XXX_Merge(src proto.Message) { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel.Merge(m, src) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) XXX_Size() int { - return xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel.Size(m) -} -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) XXX_DiscardUnknown() { - xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel.DiscardUnknown(m) -} - -var xxx_messageInfo_NetInfoResponse_Peer_ConnectionStatus_Channel proto.InternalMessageInfo - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) GetID() int32 { - if m != nil { - return m.ID - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) GetSendQueueCapacity() int64 { - if m != nil { - return m.SendQueueCapacity - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) GetSendQueueSize() int64 { - if m != nil { - return m.SendQueueSize - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) GetPriority() int64 { - if m != nil { - return m.Priority - } - return 0 -} - -func (m *NetInfoResponse_Peer_ConnectionStatus_Channel) GetRecentlySent() int64 { - if m != nil { - return m.RecentlySent - } - return 0 -} - -type StatusResponse struct { - Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty"` - LatestBlockHash string `protobuf:"bytes,1,opt,name=latest_block_hash,json=latestBlockHash,proto3" json:"latest_block_hash,omitempty"` - LatestAppHash string `protobuf:"bytes,2,opt,name=latest_app_hash,json=latestAppHash,proto3" json:"latest_app_hash,omitempty"` - LatestBlockHeight int64 `protobuf:"varint,3,opt,name=latest_block_height,json=latestBlockHeight,proto3" json:"latest_block_height,omitempty"` - LatestBlockTime string `protobuf:"bytes,4,opt,name=latest_block_time,json=latestBlockTime,proto3" json:"latest_block_time,omitempty"` - KeepLastStates int64 `protobuf:"varint,5,opt,name=keep_last_states,json=keepLastStates,proto3" json:"keep_last_states,omitempty"` - TmStatus *StatusResponse_TmStatus `protobuf:"bytes,6,opt,name=tm_status,json=tmStatus,proto3" json:"tm_status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *StatusResponse) Reset() { *m = StatusResponse{} } -func (m *StatusResponse) String() string { return proto.CompactTextString(m) } -func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{2} -} - -func (m *StatusResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StatusResponse.Unmarshal(m, b) -} -func (m *StatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StatusResponse.Marshal(b, m, deterministic) -} -func (m *StatusResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_StatusResponse.Merge(m, src) -} -func (m *StatusResponse) XXX_Size() int { - return xxx_messageInfo_StatusResponse.Size(m) -} -func (m *StatusResponse) XXX_DiscardUnknown() { - xxx_messageInfo_StatusResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_StatusResponse proto.InternalMessageInfo - -func (m *StatusResponse) GetVersion() string { - if m != nil { - return m.Version - } - return "" -} - -func (m *StatusResponse) GetLatestBlockHash() string { - if m != nil { - return m.LatestBlockHash - } - return "" -} - -func (m *StatusResponse) GetLatestAppHash() string { - if m != nil { - return m.LatestAppHash - } - return "" -} - -func (m *StatusResponse) GetLatestBlockHeight() int64 { - if m != nil { - return m.LatestBlockHeight - } - return 0 -} - -func (m *StatusResponse) GetLatestBlockTime() string { - if m != nil { - return m.LatestBlockTime - } - return "" -} - -func (m *StatusResponse) GetKeepLastStates() int64 { - if m != nil { - return m.KeepLastStates - } - return 0 -} - -func (m *StatusResponse) GetTmStatus() *StatusResponse_TmStatus { - if m != nil { - return m.TmStatus - } - return nil -} - -type StatusResponse_TmStatus struct { - NodeInfo *NodeInfo `protobuf:"bytes,3,opt,name=node_info,json=nodeInfo,proto3" json:"node_info,omitempty"` - SyncInfo *StatusResponse_TmStatus_SyncInfo `protobuf:"bytes,1,opt,name=sync_info,json=syncInfo,proto3" json:"sync_info,omitempty"` - ValidatorInfo *StatusResponse_TmStatus_ValidatorInfo `protobuf:"bytes,2,opt,name=validator_info,json=validatorInfo,proto3" json:"validator_info,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *StatusResponse_TmStatus) Reset() { *m = StatusResponse_TmStatus{} } -func (m *StatusResponse_TmStatus) String() string { return proto.CompactTextString(m) } -func (*StatusResponse_TmStatus) ProtoMessage() {} -func (*StatusResponse_TmStatus) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{2, 0} -} - -func (m *StatusResponse_TmStatus) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StatusResponse_TmStatus.Unmarshal(m, b) -} -func (m *StatusResponse_TmStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StatusResponse_TmStatus.Marshal(b, m, deterministic) -} -func (m *StatusResponse_TmStatus) XXX_Merge(src proto.Message) { - xxx_messageInfo_StatusResponse_TmStatus.Merge(m, src) -} -func (m *StatusResponse_TmStatus) XXX_Size() int { - return xxx_messageInfo_StatusResponse_TmStatus.Size(m) -} -func (m *StatusResponse_TmStatus) XXX_DiscardUnknown() { - xxx_messageInfo_StatusResponse_TmStatus.DiscardUnknown(m) -} - -var xxx_messageInfo_StatusResponse_TmStatus proto.InternalMessageInfo - -func (m *StatusResponse_TmStatus) GetNodeInfo() *NodeInfo { - if m != nil { - return m.NodeInfo - } - return nil -} - -func (m *StatusResponse_TmStatus) GetSyncInfo() *StatusResponse_TmStatus_SyncInfo { - if m != nil { - return m.SyncInfo - } - return nil -} - -func (m *StatusResponse_TmStatus) GetValidatorInfo() *StatusResponse_TmStatus_ValidatorInfo { - if m != nil { - return m.ValidatorInfo - } - return nil -} - -type StatusResponse_TmStatus_SyncInfo struct { - LatestBlockHash string `protobuf:"bytes,5,opt,name=latest_block_hash,json=latestBlockHash,proto3" json:"latest_block_hash,omitempty"` - LatestAppHash string `protobuf:"bytes,1,opt,name=latest_app_hash,json=latestAppHash,proto3" json:"latest_app_hash,omitempty"` - LatestBlockHeight int64 `protobuf:"varint,2,opt,name=latest_block_height,json=latestBlockHeight,proto3" json:"latest_block_height,omitempty"` - LatestBlockTime string `protobuf:"bytes,3,opt,name=latest_block_time,json=latestBlockTime,proto3" json:"latest_block_time,omitempty"` - CatchingUp bool `protobuf:"varint,4,opt,name=catching_up,json=catchingUp,proto3" json:"catching_up,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *StatusResponse_TmStatus_SyncInfo) Reset() { *m = StatusResponse_TmStatus_SyncInfo{} } -func (m *StatusResponse_TmStatus_SyncInfo) String() string { return proto.CompactTextString(m) } -func (*StatusResponse_TmStatus_SyncInfo) ProtoMessage() {} -func (*StatusResponse_TmStatus_SyncInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{2, 0, 0} -} - -func (m *StatusResponse_TmStatus_SyncInfo) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StatusResponse_TmStatus_SyncInfo.Unmarshal(m, b) -} -func (m *StatusResponse_TmStatus_SyncInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StatusResponse_TmStatus_SyncInfo.Marshal(b, m, deterministic) -} -func (m *StatusResponse_TmStatus_SyncInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_StatusResponse_TmStatus_SyncInfo.Merge(m, src) -} -func (m *StatusResponse_TmStatus_SyncInfo) XXX_Size() int { - return xxx_messageInfo_StatusResponse_TmStatus_SyncInfo.Size(m) -} -func (m *StatusResponse_TmStatus_SyncInfo) XXX_DiscardUnknown() { - xxx_messageInfo_StatusResponse_TmStatus_SyncInfo.DiscardUnknown(m) -} - -var xxx_messageInfo_StatusResponse_TmStatus_SyncInfo proto.InternalMessageInfo - -func (m *StatusResponse_TmStatus_SyncInfo) GetLatestBlockHash() string { - if m != nil { - return m.LatestBlockHash - } - return "" -} - -func (m *StatusResponse_TmStatus_SyncInfo) GetLatestAppHash() string { - if m != nil { - return m.LatestAppHash - } - return "" -} - -func (m *StatusResponse_TmStatus_SyncInfo) GetLatestBlockHeight() int64 { - if m != nil { - return m.LatestBlockHeight - } - return 0 -} - -func (m *StatusResponse_TmStatus_SyncInfo) GetLatestBlockTime() string { - if m != nil { - return m.LatestBlockTime - } - return "" -} - -func (m *StatusResponse_TmStatus_SyncInfo) GetCatchingUp() bool { - if m != nil { - return m.CatchingUp - } - return false -} - -type StatusResponse_TmStatus_ValidatorInfo struct { - Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"` - PubKey *StatusResponse_TmStatus_ValidatorInfo_PubKey `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty"` - VotingPower int64 `protobuf:"varint,2,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *StatusResponse_TmStatus_ValidatorInfo) Reset() { *m = StatusResponse_TmStatus_ValidatorInfo{} } -func (m *StatusResponse_TmStatus_ValidatorInfo) String() string { return proto.CompactTextString(m) } -func (*StatusResponse_TmStatus_ValidatorInfo) ProtoMessage() {} -func (*StatusResponse_TmStatus_ValidatorInfo) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{2, 0, 1} -} - -func (m *StatusResponse_TmStatus_ValidatorInfo) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo.Unmarshal(m, b) -} -func (m *StatusResponse_TmStatus_ValidatorInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo.Marshal(b, m, deterministic) -} -func (m *StatusResponse_TmStatus_ValidatorInfo) XXX_Merge(src proto.Message) { - xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo.Merge(m, src) -} -func (m *StatusResponse_TmStatus_ValidatorInfo) XXX_Size() int { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo.Size(m) -} -func (m *StatusResponse_TmStatus_ValidatorInfo) XXX_DiscardUnknown() { - xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo.DiscardUnknown(m) -} - -var xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo proto.InternalMessageInfo - -func (m *StatusResponse_TmStatus_ValidatorInfo) GetAddress() string { - if m != nil { - return m.Address - } - return "" -} - -func (m *StatusResponse_TmStatus_ValidatorInfo) GetPubKey() *StatusResponse_TmStatus_ValidatorInfo_PubKey { - if m != nil { - return m.PubKey - } - return nil -} - -func (m *StatusResponse_TmStatus_ValidatorInfo) GetVotingPower() int64 { - if m != nil { - return m.VotingPower - } - return 0 -} - -type StatusResponse_TmStatus_ValidatorInfo_PubKey struct { - Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` - Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) Reset() { - *m = StatusResponse_TmStatus_ValidatorInfo_PubKey{} -} -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) String() string { - return proto.CompactTextString(m) -} -func (*StatusResponse_TmStatus_ValidatorInfo_PubKey) ProtoMessage() {} -func (*StatusResponse_TmStatus_ValidatorInfo_PubKey) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{2, 0, 1, 0} -} - -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey.Unmarshal(m, b) -} -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey.Marshal(b, m, deterministic) -} -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) XXX_Merge(src proto.Message) { - xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey.Merge(m, src) -} -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) XXX_Size() int { - return xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey.Size(m) -} -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) XXX_DiscardUnknown() { - xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey.DiscardUnknown(m) -} - -var xxx_messageInfo_StatusResponse_TmStatus_ValidatorInfo_PubKey proto.InternalMessageInfo - -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) GetType() string { - if m != nil { - return m.Type - } - return "" -} - -func (m *StatusResponse_TmStatus_ValidatorInfo_PubKey) GetValue() string { - if m != nil { - return m.Value - } - return "" -} - -type PruneBlocksRequest struct { - FromHeight int64 `protobuf:"varint,1,opt,name=from_height,json=fromHeight,proto3" json:"from_height,omitempty"` - ToHeight int64 `protobuf:"varint,2,opt,name=to_height,json=toHeight,proto3" json:"to_height,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *PruneBlocksRequest) Reset() { *m = PruneBlocksRequest{} } -func (m *PruneBlocksRequest) String() string { return proto.CompactTextString(m) } -func (*PruneBlocksRequest) ProtoMessage() {} -func (*PruneBlocksRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{3} -} - -func (m *PruneBlocksRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_PruneBlocksRequest.Unmarshal(m, b) -} -func (m *PruneBlocksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_PruneBlocksRequest.Marshal(b, m, deterministic) -} -func (m *PruneBlocksRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_PruneBlocksRequest.Merge(m, src) -} -func (m *PruneBlocksRequest) XXX_Size() int { - return xxx_messageInfo_PruneBlocksRequest.Size(m) -} -func (m *PruneBlocksRequest) XXX_DiscardUnknown() { - xxx_messageInfo_PruneBlocksRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_PruneBlocksRequest proto.InternalMessageInfo - -func (m *PruneBlocksRequest) GetFromHeight() int64 { - if m != nil { - return m.FromHeight - } - return 0 -} - -func (m *PruneBlocksRequest) GetToHeight() int64 { - if m != nil { - return m.ToHeight - } - return 0 -} - -type DealPeerRequest struct { - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Persistent bool `protobuf:"varint,2,opt,name=persistent,proto3" json:"persistent,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *DealPeerRequest) Reset() { *m = DealPeerRequest{} } -func (m *DealPeerRequest) String() string { return proto.CompactTextString(m) } -func (*DealPeerRequest) ProtoMessage() {} -func (*DealPeerRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{4} -} - -func (m *DealPeerRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DealPeerRequest.Unmarshal(m, b) -} -func (m *DealPeerRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DealPeerRequest.Marshal(b, m, deterministic) -} -func (m *DealPeerRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_DealPeerRequest.Merge(m, src) -} -func (m *DealPeerRequest) XXX_Size() int { - return xxx_messageInfo_DealPeerRequest.Size(m) -} -func (m *DealPeerRequest) XXX_DiscardUnknown() { - xxx_messageInfo_DealPeerRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_DealPeerRequest proto.InternalMessageInfo - -func (m *DealPeerRequest) GetAddress() string { - if m != nil { - return m.Address - } - return "" -} - -func (m *DealPeerRequest) GetPersistent() bool { - if m != nil { - return m.Persistent - } - return false -} - -type DashboardResponse struct { - LatestHeight int64 `protobuf:"varint,1,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height,omitempty"` - Timestamp *timestamp.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Duration int64 `protobuf:"varint,3,opt,name=duration,proto3" json:"duration,omitempty"` - MemoryUsage uint64 `protobuf:"varint,4,opt,name=memory_usage,json=memoryUsage,proto3" json:"memory_usage,omitempty"` - ValidatorPubKey string `protobuf:"bytes,5,opt,name=validator_pub_key,json=validatorPubKey,proto3" json:"validator_pub_key,omitempty"` - MaxPeerHeight int64 `protobuf:"varint,6,opt,name=max_peer_height,json=maxPeerHeight,proto3" json:"max_peer_height,omitempty"` - PeersCount int32 `protobuf:"varint,7,opt,name=peers_count,json=peersCount,proto3" json:"peers_count,omitempty"` - AvgBlockProcessingTime int64 `protobuf:"varint,8,opt,name=avg_block_processing_time,json=avgBlockProcessingTime,proto3" json:"avg_block_processing_time,omitempty"` - TimePerBlock int64 `protobuf:"varint,9,opt,name=time_per_block,json=timePerBlock,proto3" json:"time_per_block,omitempty"` - MissedBlocks string `protobuf:"bytes,10,opt,name=missed_blocks,json=missedBlocks,proto3" json:"missed_blocks,omitempty"` - VotingPower int64 `protobuf:"varint,11,opt,name=voting_power,json=votingPower,proto3" json:"voting_power,omitempty"` - Stake string `protobuf:"bytes,12,opt,name=stake,proto3" json:"stake,omitempty"` - ValidatorStatus DashboardResponse_ValidatorStatus `protobuf:"varint,13,opt,name=validator_status,json=validatorStatus,proto3,enum=pb.DashboardResponse_ValidatorStatus" json:"validator_status,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *DashboardResponse) Reset() { *m = DashboardResponse{} } -func (m *DashboardResponse) String() string { return proto.CompactTextString(m) } -func (*DashboardResponse) ProtoMessage() {} -func (*DashboardResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_cde9ec64f0d2c859, []int{5} -} - -func (m *DashboardResponse) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_DashboardResponse.Unmarshal(m, b) -} -func (m *DashboardResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_DashboardResponse.Marshal(b, m, deterministic) -} -func (m *DashboardResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_DashboardResponse.Merge(m, src) -} -func (m *DashboardResponse) XXX_Size() int { - return xxx_messageInfo_DashboardResponse.Size(m) -} -func (m *DashboardResponse) XXX_DiscardUnknown() { - xxx_messageInfo_DashboardResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_DashboardResponse proto.InternalMessageInfo - -func (m *DashboardResponse) GetLatestHeight() int64 { - if m != nil { - return m.LatestHeight - } - return 0 -} - -func (m *DashboardResponse) GetTimestamp() *timestamp.Timestamp { - if m != nil { - return m.Timestamp - } - return nil -} - -func (m *DashboardResponse) GetDuration() int64 { - if m != nil { - return m.Duration - } - return 0 -} - -func (m *DashboardResponse) GetMemoryUsage() uint64 { - if m != nil { - return m.MemoryUsage - } - return 0 -} - -func (m *DashboardResponse) GetValidatorPubKey() string { - if m != nil { - return m.ValidatorPubKey - } - return "" -} - -func (m *DashboardResponse) GetMaxPeerHeight() int64 { - if m != nil { - return m.MaxPeerHeight - } - return 0 -} - -func (m *DashboardResponse) GetPeersCount() int32 { - if m != nil { - return m.PeersCount - } - return 0 -} - -func (m *DashboardResponse) GetAvgBlockProcessingTime() int64 { - if m != nil { - return m.AvgBlockProcessingTime - } - return 0 -} - -func (m *DashboardResponse) GetTimePerBlock() int64 { - if m != nil { - return m.TimePerBlock - } - return 0 -} - -func (m *DashboardResponse) GetMissedBlocks() string { - if m != nil { - return m.MissedBlocks - } - return "" -} - -func (m *DashboardResponse) GetVotingPower() int64 { - if m != nil { - return m.VotingPower - } - return 0 -} - -func (m *DashboardResponse) GetStake() string { - if m != nil { - return m.Stake - } - return "" -} - -func (m *DashboardResponse) GetValidatorStatus() DashboardResponse_ValidatorStatus { - if m != nil { - return m.ValidatorStatus - } - return DashboardResponse_Validating -} - -func init() { - proto.RegisterEnum("pb.DashboardResponse_ValidatorStatus", DashboardResponse_ValidatorStatus_name, DashboardResponse_ValidatorStatus_value) - proto.RegisterType((*NodeInfo)(nil), "pb.NodeInfo") - proto.RegisterType((*NodeInfo_ProtocolVersion)(nil), "pb.NodeInfo.ProtocolVersion") - proto.RegisterType((*NodeInfo_Other)(nil), "pb.NodeInfo.Other") - proto.RegisterType((*NetInfoResponse)(nil), "pb.NetInfoResponse") - proto.RegisterType((*NetInfoResponse_Peer)(nil), "pb.NetInfoResponse.Peer") - proto.RegisterType((*NetInfoResponse_Peer_ConnectionStatus)(nil), "pb.NetInfoResponse.Peer.ConnectionStatus") - proto.RegisterType((*NetInfoResponse_Peer_ConnectionStatus_Monitor)(nil), "pb.NetInfoResponse.Peer.ConnectionStatus.Monitor") - proto.RegisterType((*NetInfoResponse_Peer_ConnectionStatus_Channel)(nil), "pb.NetInfoResponse.Peer.ConnectionStatus.Channel") - proto.RegisterType((*StatusResponse)(nil), "pb.StatusResponse") - proto.RegisterType((*StatusResponse_TmStatus)(nil), "pb.StatusResponse.TmStatus") - proto.RegisterType((*StatusResponse_TmStatus_SyncInfo)(nil), "pb.StatusResponse.TmStatus.SyncInfo") - proto.RegisterType((*StatusResponse_TmStatus_ValidatorInfo)(nil), "pb.StatusResponse.TmStatus.ValidatorInfo") - proto.RegisterType((*StatusResponse_TmStatus_ValidatorInfo_PubKey)(nil), "pb.StatusResponse.TmStatus.ValidatorInfo.PubKey") - proto.RegisterType((*PruneBlocksRequest)(nil), "pb.PruneBlocksRequest") - proto.RegisterType((*DealPeerRequest)(nil), "pb.DealPeerRequest") - proto.RegisterType((*DashboardResponse)(nil), "pb.DashboardResponse") -} - -func init() { proto.RegisterFile("manager.proto", fileDescriptor_cde9ec64f0d2c859) } - -var fileDescriptor_cde9ec64f0d2c859 = []byte{ - // 1543 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0x23, 0x49, - 0x15, 0xc6, 0xee, 0xd8, 0x6e, 0x9f, 0x4e, 0x62, 0xa7, 0x16, 0x06, 0x6f, 0xcf, 0x8a, 0x0c, 0x66, - 0x58, 0x65, 0x11, 0xf2, 0x2e, 0x06, 0xf1, 0x23, 0xc1, 0x45, 0x36, 0x41, 0x60, 0x0d, 0x33, 0x63, - 0xca, 0xb3, 0x73, 0xdb, 0x2a, 0x77, 0x9f, 0xd8, 0xad, 0x74, 0x57, 0x15, 0xdd, 0xd5, 0xde, 0x98, - 0x27, 0xe0, 0x9a, 0xc7, 0x40, 0x42, 0xdc, 0x20, 0xf1, 0x10, 0x5c, 0x73, 0xc5, 0x7b, 0x20, 0x71, - 0x87, 0xea, 0xa7, 0xfd, 0x13, 0x27, 0xa3, 0x19, 0xae, 0xd2, 0xe7, 0x3b, 0xa7, 0x3e, 0x9f, 0xfa, - 0xea, 0x9c, 0x53, 0x15, 0x38, 0xc9, 0x19, 0x67, 0x0b, 0x2c, 0x46, 0xb2, 0x10, 0x4a, 0x90, 0xa6, - 0x9c, 0x87, 0x4f, 0x17, 0x42, 0x2c, 0x32, 0xfc, 0xdc, 0x20, 0xf3, 0xea, 0xe6, 0x73, 0xcc, 0xa5, - 0x5a, 0xdb, 0x80, 0xf0, 0xfc, 0xbe, 0x53, 0xa5, 0x39, 0x96, 0x8a, 0xe5, 0xd2, 0x06, 0x0c, 0xff, - 0xe2, 0x81, 0xff, 0x4a, 0x24, 0x38, 0xe1, 0x37, 0x82, 0xfc, 0x06, 0xfa, 0x06, 0x8d, 0x45, 0x16, - 0xad, 0xb0, 0x28, 0x53, 0xc1, 0x07, 0xfe, 0xb3, 0xc6, 0x45, 0x30, 0xfe, 0x64, 0x24, 0xe7, 0xa3, - 0x3a, 0x6e, 0x34, 0x75, 0x41, 0x6f, 0x6d, 0x0c, 0xed, 0xc9, 0x7d, 0x80, 0x9c, 0x42, 0x33, 0x4d, - 0x06, 0x8d, 0x67, 0x8d, 0x8b, 0x2e, 0x6d, 0xa6, 0x09, 0x39, 0x87, 0x20, 0x4b, 0x4b, 0x85, 0x3c, - 0x62, 0x49, 0x52, 0x0c, 0x9a, 0xc6, 0x01, 0x16, 0xba, 0x4c, 0x92, 0x82, 0x0c, 0xa0, 0xc3, 0x51, - 0x7d, 0x2d, 0x8a, 0xdb, 0x81, 0x67, 0x9c, 0xb5, 0xa9, 0x3d, 0x75, 0x2a, 0x47, 0xd6, 0xe3, 0x4c, - 0x12, 0x82, 0x1f, 0x2f, 0x19, 0xe7, 0x98, 0x95, 0x83, 0x96, 0x71, 0x6d, 0x6c, 0xbd, 0x2a, 0x17, - 0x3c, 0xbd, 0xc5, 0x62, 0xd0, 0xb6, 0xab, 0x9c, 0x49, 0x2e, 0xa0, 0x25, 0xd4, 0x12, 0x8b, 0x41, - 0xc7, 0x6c, 0x8c, 0xec, 0x6d, 0xec, 0xb5, 0xf6, 0x50, 0x1b, 0x10, 0xbe, 0x80, 0xde, 0xbd, 0x8d, - 0x92, 0x3e, 0x78, 0x72, 0x2c, 0x4d, 0x8a, 0x47, 0x54, 0x7f, 0x92, 0x6f, 0x42, 0x6b, 0x9e, 0x89, - 0xf8, 0xd6, 0x6c, 0xf6, 0x88, 0x5a, 0x43, 0xc7, 0x31, 0x29, 0xcd, 0x3e, 0x8f, 0xa8, 0xfe, 0x0c, - 0xaf, 0xa0, 0x65, 0xc8, 0xc9, 0xc7, 0xe0, 0xab, 0xbb, 0x28, 0xe5, 0x09, 0xde, 0x39, 0x1d, 0x3a, - 0xea, 0x6e, 0xa2, 0x4d, 0xad, 0x52, 0x21, 0x63, 0x23, 0x11, 0x96, 0xa5, 0x93, 0x0f, 0x0a, 0x19, - 0x5f, 0x5a, 0x64, 0xf8, 0xe7, 0x2e, 0xf4, 0x5e, 0xa1, 0xd2, 0xa9, 0x52, 0x2c, 0xa5, 0xe0, 0x25, - 0x92, 0x4f, 0xa0, 0x6b, 0x75, 0x4c, 0xf9, 0xc2, 0x28, 0xe4, 0xd3, 0x2d, 0xb0, 0xf5, 0x62, 0xa1, - 0x09, 0xbd, 0x8b, 0x2e, 0xdd, 0x02, 0xe4, 0xdb, 0xd0, 0xe1, 0x91, 0x44, 0xed, 0xd3, 0xa9, 0x78, - 0xb4, 0xcd, 0xa7, 0xda, 0x22, 0x23, 0x68, 0x59, 0xd8, 0x7b, 0xe6, 0x5d, 0x04, 0xe3, 0x81, 0x11, - 0x69, 0xff, 0x87, 0x47, 0x3a, 0x92, 0xda, 0xb0, 0xf0, 0xbf, 0x1d, 0x38, 0xd2, 0x36, 0xf9, 0x0c, - 0xba, 0x5c, 0x24, 0x18, 0xa5, 0xfc, 0x46, 0x98, 0x6c, 0x82, 0xf1, 0xf1, 0xae, 0xc2, 0xd4, 0xe7, - 0x75, 0xb1, 0x9d, 0x43, 0x90, 0x96, 0x91, 0xa8, 0xd4, 0x5c, 0x54, 0xdc, 0x16, 0x8b, 0x4f, 0x21, - 0x2d, 0x5f, 0x3b, 0x84, 0xbc, 0x85, 0xb3, 0x58, 0x70, 0x8e, 0xb1, 0x4a, 0x05, 0x8f, 0x4a, 0xc5, - 0x54, 0x65, 0xf3, 0x0c, 0xc6, 0x9f, 0x3d, 0x96, 0xd0, 0xe8, 0x6a, 0xb3, 0x62, 0x66, 0x16, 0xd0, - 0x7e, 0x7c, 0x0f, 0x21, 0x4f, 0xa1, 0x5b, 0x60, 0x2e, 0x14, 0x46, 0xa9, 0x74, 0xd5, 0xe6, 0x5b, - 0x60, 0x22, 0xc3, 0xbf, 0xb5, 0xa1, 0x7f, 0x9f, 0x43, 0x57, 0xda, 0x75, 0x55, 0x30, 0x55, 0x17, - 0xa1, 0x47, 0x37, 0x36, 0x99, 0x41, 0x30, 0x43, 0x9e, 0xbc, 0x14, 0x3c, 0x55, 0xa2, 0x30, 0xdb, - 0x08, 0xc6, 0x3f, 0x7a, 0xef, 0xfc, 0x46, 0x6e, 0x21, 0xdd, 0x65, 0xd1, 0xa4, 0x14, 0xe3, 0x55, - 0x4d, 0xda, 0xfc, 0xbf, 0x49, 0x77, 0x58, 0xc8, 0x4b, 0xf0, 0xaf, 0xea, 0x7e, 0xb1, 0xe7, 0xfa, - 0x01, 0x8c, 0x6e, 0x25, 0xdd, 0x50, 0x84, 0xff, 0x6a, 0x42, 0xa7, 0xa6, 0x7e, 0x02, 0xed, 0xcb, - 0x58, 0xa5, 0x2b, 0x1c, 0x9c, 0x98, 0x63, 0x74, 0x96, 0xee, 0x8e, 0x99, 0x62, 0x85, 0x72, 0xb5, - 0x6c, 0x8d, 0x3d, 0x39, 0x9b, 0xf7, 0xe4, 0x24, 0x70, 0x34, 0x49, 0x32, 0x34, 0xe7, 0xe2, 0x51, - 0xf3, 0xad, 0x59, 0xbe, 0x5c, 0x2b, 0x2c, 0x9d, 0xf6, 0xd6, 0xd0, 0x2d, 0x3e, 0x63, 0xb9, 0xcc, - 0xd0, 0x76, 0xbf, 0x47, 0x6b, 0x53, 0xf3, 0x4f, 0x78, 0xa9, 0x28, 0x53, 0x68, 0xba, 0xdf, 0xa3, - 0x1b, 0x5b, 0xaf, 0xba, 0xaa, 0x0a, 0xe3, 0xea, 0xd8, 0x55, 0xce, 0xd4, 0x9e, 0xcb, 0xd5, 0xc2, - 0x78, 0x7c, 0xeb, 0x71, 0xa6, 0xe6, 0x9b, 0x22, 0xbb, 0x35, 0xae, 0xae, 0xe5, 0xab, 0x6d, 0xed, - 0x33, 0xe9, 0x50, 0xcc, 0x07, 0x60, 0x7d, 0xb5, 0xad, 0x19, 0xdf, 0xa4, 0x39, 0x6a, 0x57, 0x60, - 0x19, 0x9d, 0x69, 0x18, 0x0b, 0xb1, 0x30, 0x6d, 0x7e, 0xfc, 0xac, 0x71, 0x71, 0x42, 0x37, 0x76, - 0xf8, 0xd7, 0x06, 0x74, 0x9c, 0xc8, 0x7a, 0x8e, 0x4e, 0xae, 0xcd, 0xf6, 0x5a, 0xb4, 0x39, 0xb9, - 0x26, 0x3f, 0x84, 0x33, 0x5d, 0x26, 0xbf, 0xaf, 0xb0, 0xc2, 0x2b, 0x26, 0x59, 0x9c, 0xaa, 0xb5, - 0xd1, 0xd6, 0xa3, 0x87, 0x0e, 0xf2, 0x1c, 0x4e, 0x36, 0xe0, 0x2c, 0xfd, 0x23, 0x3a, 0xb1, 0xf7, - 0x41, 0x9b, 0x4b, 0x2a, 0x0a, 0x4d, 0xe5, 0xb9, 0xdd, 0x39, 0x9b, 0x0c, 0xe1, 0x98, 0x62, 0x8c, - 0x5c, 0x65, 0xeb, 0x19, 0x72, 0xe5, 0x0e, 0x60, 0x0f, 0x1b, 0xfe, 0xa3, 0x03, 0xa7, 0xae, 0xd7, - 0xea, 0x99, 0xb4, 0x33, 0xb3, 0x3b, 0xfb, 0x33, 0xfb, 0x07, 0x70, 0x96, 0x31, 0x85, 0xa5, 0x8a, - 0xcc, 0xa0, 0x8c, 0x96, 0xac, 0x5c, 0xba, 0xe2, 0xe8, 0x59, 0xc7, 0x97, 0x1a, 0xff, 0x2d, 0x2b, - 0x97, 0xe4, 0x53, 0x70, 0x50, 0xc4, 0xa4, 0xb4, 0x91, 0x76, 0x60, 0x9e, 0x58, 0xf8, 0x52, 0x4a, - 0x13, 0x37, 0x82, 0x8f, 0xf6, 0x39, 0x31, 0x5d, 0x2c, 0x95, 0xdb, 0xcb, 0xd9, 0x2e, 0xab, 0x71, - 0x1c, 0xe4, 0xa0, 0xaf, 0x44, 0x77, 0xb7, 0xec, 0xe6, 0xa0, 0xcf, 0x8a, 0x5c, 0x40, 0xff, 0x16, - 0x51, 0x46, 0x19, 0x2b, 0x95, 0x19, 0x41, 0x9b, 0x6a, 0x3b, 0xd5, 0xf8, 0xef, 0x58, 0xa9, 0x66, - 0x06, 0x25, 0x3f, 0x87, 0xae, 0xca, 0xeb, 0x29, 0xd5, 0x36, 0x0d, 0xfb, 0x54, 0xb7, 0xd7, 0xbe, - 0x34, 0xa3, 0x37, 0xb9, 0x03, 0x7c, 0xe5, 0xbe, 0xc2, 0xff, 0x1c, 0x81, 0x5f, 0xc3, 0xfb, 0x03, - 0xd4, 0x7b, 0xe7, 0x00, 0xbd, 0x84, 0x6e, 0xb9, 0xe6, 0xb1, 0x0d, 0xb5, 0x73, 0xe7, 0xf9, 0x3b, - 0x7e, 0x71, 0x34, 0x5b, 0xf3, 0xd8, 0x52, 0x94, 0xee, 0x8b, 0x4c, 0xe1, 0x74, 0xc5, 0xb2, 0x34, - 0x61, 0x4a, 0x14, 0x96, 0x67, 0x67, 0xbe, 0x3e, 0xc6, 0xf3, 0xb6, 0x5e, 0x61, 0xc8, 0x4e, 0x56, - 0xbb, 0x66, 0xf8, 0xef, 0x06, 0xf8, 0xf5, 0x0f, 0x3d, 0x7c, 0xda, 0xad, 0xf7, 0x3e, 0xed, 0xc6, - 0x07, 0x9c, 0x76, 0xf3, 0x83, 0x4e, 0xdb, 0x7b, 0xf8, 0xb4, 0xcf, 0x21, 0x88, 0x99, 0x8a, 0x97, - 0x29, 0x5f, 0x44, 0x95, 0x74, 0xb7, 0x29, 0xd4, 0xd0, 0x57, 0x32, 0xfc, 0x67, 0x03, 0x4e, 0xf6, - 0xb6, 0xaf, 0x4b, 0xbd, 0xbe, 0xaf, 0xdd, 0xc3, 0xc5, 0x99, 0x64, 0x02, 0x1d, 0x59, 0xcd, 0xa3, - 0x5b, 0x5c, 0xbb, 0xc3, 0xf9, 0xe2, 0xbd, 0x45, 0x1d, 0x4d, 0xab, 0xf9, 0x0b, 0x5c, 0xd3, 0xb6, - 0x34, 0x7f, 0xc9, 0x77, 0xe1, 0x78, 0x25, 0x94, 0xce, 0x4a, 0x8a, 0xaf, 0xb1, 0x70, 0x9b, 0x0d, - 0x2c, 0x36, 0xd5, 0x50, 0x38, 0x86, 0xb6, 0x5d, 0xa4, 0x27, 0xa8, 0x5a, 0x4b, 0x74, 0xbd, 0x62, - 0xbe, 0xf5, 0x04, 0x5d, 0xb1, 0xac, 0xc2, 0x7a, 0x0e, 0x1b, 0x63, 0x48, 0x81, 0x4c, 0x8b, 0x8a, - 0xa3, 0x11, 0xa0, 0xa4, 0xf8, 0x87, 0x0a, 0x4b, 0xa5, 0x45, 0xb8, 0x29, 0x44, 0x5e, 0x0b, 0x6b, - 0xa7, 0x0b, 0x68, 0xc8, 0x29, 0xfa, 0x14, 0xba, 0x4a, 0xec, 0xeb, 0xee, 0x2b, 0x61, 0x9d, 0xc3, - 0x17, 0xd0, 0xbb, 0x46, 0x96, 0x99, 0xc7, 0x81, 0x23, 0xdc, 0x91, 0xa8, 0xb1, 0x2f, 0xd1, 0x77, - 0x00, 0xa4, 0x1e, 0x0c, 0xfa, 0x3d, 0x62, 0xa9, 0x7c, 0xba, 0x83, 0x0c, 0xff, 0xd4, 0x82, 0xb3, - 0x6b, 0x56, 0x2e, 0xe7, 0x82, 0x15, 0xc9, 0x66, 0xba, 0x7c, 0x0f, 0x5c, 0x49, 0xec, 0xa7, 0x78, - 0x6c, 0x41, 0x97, 0xa4, 0x6e, 0xc7, 0xfa, 0xa9, 0xeb, 0x8a, 0x3a, 0x1c, 0xd9, 0xc7, 0xf0, 0xa8, - 0x7e, 0x0c, 0x8f, 0xde, 0xd4, 0x11, 0x74, 0x1b, 0xac, 0xe7, 0x61, 0x52, 0xdf, 0x4e, 0x6e, 0x1e, - 0xd6, 0xb6, 0x3e, 0x88, 0x1c, 0x73, 0x51, 0xac, 0xa3, 0xaa, 0x64, 0x0b, 0x3b, 0x35, 0x8e, 0x68, - 0x60, 0xb1, 0xaf, 0x34, 0xa4, 0xeb, 0x6d, 0xdb, 0x52, 0x75, 0x01, 0xb8, 0x9a, 0xdf, 0x38, 0xdc, - 0x51, 0x7d, 0x0a, 0xbd, 0x9c, 0xdd, 0x99, 0x17, 0x58, 0xbd, 0x17, 0x7b, 0x5f, 0x9d, 0xe4, 0xec, - 0x4e, 0x4b, 0xe8, 0x36, 0x73, 0x0e, 0x81, 0x79, 0x67, 0x45, 0xb1, 0xa8, 0xb8, 0x32, 0x33, 0xb5, - 0xa5, 0x85, 0xc2, 0xa2, 0xbc, 0xd2, 0x08, 0xf9, 0x05, 0x7c, 0xcc, 0x56, 0x0b, 0x57, 0xe1, 0xb2, - 0x10, 0x31, 0x96, 0xa5, 0x2e, 0x17, 0x53, 0xec, 0xf6, 0x36, 0x7b, 0xc2, 0x56, 0x0b, 0x73, 0xd0, - 0xd3, 0x8d, 0xdb, 0xd4, 0xfc, 0x73, 0x38, 0xd5, 0x51, 0x91, 0xc4, 0xc2, 0xae, 0x77, 0x57, 0xdc, - 0xb1, 0x46, 0xa7, 0x58, 0x98, 0x35, 0x5a, 0xf3, 0x3c, 0x2d, 0x4b, 0x4c, 0x6c, 0x4c, 0x69, 0xee, - 0xba, 0x2e, 0x3d, 0xb6, 0xa0, 0x2d, 0xa0, 0x83, 0x32, 0x0d, 0x0e, 0xca, 0x54, 0x17, 0x62, 0xa9, - 0xd8, 0x2d, 0x9a, 0x5b, 0xaf, 0x4b, 0xad, 0x41, 0xa6, 0xd0, 0xdf, 0x6a, 0xe6, 0x46, 0xa8, 0x7e, - 0x48, 0x9c, 0x8e, 0xbf, 0xaf, 0x7b, 0xe6, 0xa0, 0x04, 0xb6, 0xdd, 0xe2, 0xda, 0x69, 0xab, 0xac, - 0x05, 0x86, 0xaf, 0xa1, 0x77, 0x2f, 0x86, 0x9c, 0x02, 0x38, 0x28, 0xe5, 0x8b, 0xfe, 0x37, 0xb4, - 0x7d, 0xb5, 0x64, 0x59, 0x86, 0x7c, 0x81, 0x45, 0xbf, 0x41, 0x02, 0xe8, 0xbc, 0xbe, 0xb9, 0xc9, - 0x52, 0x8e, 0xfd, 0x26, 0xe9, 0x41, 0xf0, 0x4a, 0xa8, 0x6b, 0x8c, 0x33, 0x56, 0x60, 0xd2, 0xf7, - 0xc6, 0x7f, 0x6f, 0xc2, 0xe9, 0x4b, 0xfb, 0xbf, 0xd7, 0x0c, 0x8b, 0x55, 0x1a, 0x23, 0xf9, 0x09, - 0xb4, 0x1d, 0xf5, 0x93, 0x83, 0xca, 0xfa, 0xb5, 0xfe, 0x1f, 0x2c, 0x24, 0x87, 0x1d, 0x4f, 0x7e, - 0x0a, 0x1d, 0xf7, 0xe2, 0x7a, 0x74, 0xd9, 0x47, 0x0f, 0x3c, 0xcb, 0xc8, 0xaf, 0x20, 0xd8, 0x69, - 0x56, 0xf2, 0x44, 0xc7, 0x1c, 0x76, 0x6f, 0xf8, 0x08, 0x27, 0xf9, 0x19, 0xf8, 0x75, 0x5f, 0x12, - 0xc3, 0x7f, 0xaf, 0x4b, 0x1f, 0x5d, 0xf8, 0x4b, 0xe8, 0x6e, 0xf4, 0x7f, 0x34, 0xe3, 0x6f, 0x3d, - 0x78, 0x4c, 0x5f, 0x34, 0xe6, 0x6d, 0x13, 0xf8, 0xe3, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0xd5, - 0x86, 0xa6, 0x3b, 0xb8, 0x0e, 0x00, 0x00, -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// ManagerServiceClient is the client API for ManagerService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type ManagerServiceClient interface { - Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error) - NetInfo(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*NetInfoResponse, error) - PruneBlocks(ctx context.Context, in *PruneBlocksRequest, opts ...grpc.CallOption) (*empty.Empty, error) - DealPeer(ctx context.Context, in *DealPeerRequest, opts ...grpc.CallOption) (*empty.Empty, error) - Dashboard(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (ManagerService_DashboardClient, error) -} - -type managerServiceClient struct { - cc *grpc.ClientConn -} - -func NewManagerServiceClient(cc *grpc.ClientConn) ManagerServiceClient { - return &managerServiceClient{cc} -} - -func (c *managerServiceClient) Status(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*StatusResponse, error) { - out := new(StatusResponse) - err := c.cc.Invoke(ctx, "/pb.ManagerService/Status", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *managerServiceClient) NetInfo(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (*NetInfoResponse, error) { - out := new(NetInfoResponse) - err := c.cc.Invoke(ctx, "/pb.ManagerService/NetInfo", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *managerServiceClient) PruneBlocks(ctx context.Context, in *PruneBlocksRequest, opts ...grpc.CallOption) (*empty.Empty, error) { - out := new(empty.Empty) - err := c.cc.Invoke(ctx, "/pb.ManagerService/PruneBlocks", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *managerServiceClient) DealPeer(ctx context.Context, in *DealPeerRequest, opts ...grpc.CallOption) (*empty.Empty, error) { - out := new(empty.Empty) - err := c.cc.Invoke(ctx, "/pb.ManagerService/DealPeer", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *managerServiceClient) Dashboard(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (ManagerService_DashboardClient, error) { - stream, err := c.cc.NewStream(ctx, &_ManagerService_serviceDesc.Streams[0], "/pb.ManagerService/Dashboard", opts...) - if err != nil { - return nil, err - } - x := &managerServiceDashboardClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type ManagerService_DashboardClient interface { - Recv() (*DashboardResponse, error) - grpc.ClientStream -} - -type managerServiceDashboardClient struct { - grpc.ClientStream -} - -func (x *managerServiceDashboardClient) Recv() (*DashboardResponse, error) { - m := new(DashboardResponse) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - -// ManagerServiceServer is the server API for ManagerService service. -type ManagerServiceServer interface { - Status(context.Context, *empty.Empty) (*StatusResponse, error) - NetInfo(context.Context, *empty.Empty) (*NetInfoResponse, error) - PruneBlocks(context.Context, *PruneBlocksRequest) (*empty.Empty, error) - DealPeer(context.Context, *DealPeerRequest) (*empty.Empty, error) - Dashboard(*empty.Empty, ManagerService_DashboardServer) error -} - -// UnimplementedManagerServiceServer can be embedded to have forward compatible implementations. -type UnimplementedManagerServiceServer struct { -} - -func (*UnimplementedManagerServiceServer) Status(ctx context.Context, req *empty.Empty) (*StatusResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Status not implemented") -} -func (*UnimplementedManagerServiceServer) NetInfo(ctx context.Context, req *empty.Empty) (*NetInfoResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method NetInfo not implemented") -} -func (*UnimplementedManagerServiceServer) PruneBlocks(ctx context.Context, req *PruneBlocksRequest) (*empty.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method PruneBlocks not implemented") -} -func (*UnimplementedManagerServiceServer) DealPeer(ctx context.Context, req *DealPeerRequest) (*empty.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method DealPeer not implemented") -} -func (*UnimplementedManagerServiceServer) Dashboard(req *empty.Empty, srv ManagerService_DashboardServer) error { - return status.Errorf(codes.Unimplemented, "method Dashboard not implemented") -} - -func RegisterManagerServiceServer(s *grpc.Server, srv ManagerServiceServer) { - s.RegisterService(&_ManagerService_serviceDesc, srv) -} - -func _ManagerService_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(empty.Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ManagerServiceServer).Status(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/pb.ManagerService/Status", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ManagerServiceServer).Status(ctx, req.(*empty.Empty)) - } - return interceptor(ctx, in, info, handler) -} - -func _ManagerService_NetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(empty.Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ManagerServiceServer).NetInfo(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/pb.ManagerService/NetInfo", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ManagerServiceServer).NetInfo(ctx, req.(*empty.Empty)) - } - return interceptor(ctx, in, info, handler) -} - -func _ManagerService_PruneBlocks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(PruneBlocksRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ManagerServiceServer).PruneBlocks(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/pb.ManagerService/PruneBlocks", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ManagerServiceServer).PruneBlocks(ctx, req.(*PruneBlocksRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ManagerService_DealPeer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DealPeerRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ManagerServiceServer).DealPeer(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/pb.ManagerService/DealPeer", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ManagerServiceServer).DealPeer(ctx, req.(*DealPeerRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ManagerService_Dashboard_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(empty.Empty) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(ManagerServiceServer).Dashboard(m, &managerServiceDashboardServer{stream}) -} - -type ManagerService_DashboardServer interface { - Send(*DashboardResponse) error - grpc.ServerStream -} - -type managerServiceDashboardServer struct { - grpc.ServerStream -} - -func (x *managerServiceDashboardServer) Send(m *DashboardResponse) error { - return x.ServerStream.SendMsg(m) -} - -var _ManagerService_serviceDesc = grpc.ServiceDesc{ - ServiceName: "pb.ManagerService", - HandlerType: (*ManagerServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "Status", - Handler: _ManagerService_Status_Handler, - }, - { - MethodName: "NetInfo", - Handler: _ManagerService_NetInfo_Handler, - }, - { - MethodName: "PruneBlocks", - Handler: _ManagerService_PruneBlocks_Handler, - }, - { - MethodName: "DealPeer", - Handler: _ManagerService_DealPeer_Handler, - }, - }, - Streams: []grpc.StreamDesc{ - { - StreamName: "Dashboard", - Handler: _ManagerService_Dashboard_Handler, - ServerStreams: true, - }, - }, - Metadata: "manager.proto", -} diff --git a/cli/service/client.go b/cli/service/client.go index c81d1da29..66922e273 100644 --- a/cli/service/client.go +++ b/cli/service/client.go @@ -1,19 +1,20 @@ package service import ( - "bytes" "context" "fmt" - "github.com/MinterTeam/minter-go-node/cli/pb" + pb "github.com/MinterTeam/minter-go-node/cli/cli_pb" "github.com/c-bata/go-prompt" - "github.com/golang/protobuf/jsonpb" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/empty" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" "github.com/marcusolsson/tui-go" "github.com/urfave/cli/v2" "google.golang.org/grpc" + "google.golang.org/protobuf/encoding/protojson" "io" + "log" "os" "strings" "time" @@ -23,7 +24,7 @@ type ManagerConsole struct { cli *cli.App } -func NewManagerConsole(cli *cli.App) *ManagerConsole { +func newManagerConsole(cli *cli.App) *ManagerConsole { return &ManagerConsole{cli: cli} } @@ -106,8 +107,11 @@ func (mc *ManagerConsole) Cli(ctx context.Context) { } } -func ConfigureManagerConsole(socketPath string) (*ManagerConsole, error) { - cc, err := grpc.Dial("passthrough:///unix:///"+socketPath, grpc.WithInsecure()) +func NewCLI(socketPath string) (*ManagerConsole, error) { + cc, err := grpc.Dial("passthrough:///unix:///"+socketPath, + grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor()), + grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor()), + grpc.WithInsecure()) if err != nil { return nil, err } @@ -116,7 +120,7 @@ func ConfigureManagerConsole(socketPath string) (*ManagerConsole, error) { app := cli.NewApp() app.CommandNotFound = func(ctx *cli.Context, cmd string) { - fmt.Println(fmt.Sprintf("No help topic for '%v'", cmd)) + fmt.Printf("No help topic for '%v'\n", cmd) } app.UseShortOptionHandling = true jsonFlag := &cli.BoolFlag{Name: "json", Aliases: []string{"j"}, Required: false, Usage: "echo in json format"} @@ -163,7 +167,7 @@ func ConfigureManagerConsole(socketPath string) (*ManagerConsole, error) { { Name: "dashboard", Aliases: []string{"db"}, - Usage: "Show dashboard", //todo + Usage: "Show dashboard", Action: dashboardCMD(client), }, { @@ -179,7 +183,7 @@ func ConfigureManagerConsole(socketPath string) (*ManagerConsole, error) { } app.Setup() - return NewManagerConsole(app), nil + return newManagerConsole(app), nil } func exitCMD(_ *cli.Context) error { @@ -190,13 +194,13 @@ func exitCMD(_ *cli.Context) error { func dashboardCMD(client pb.ManagerServiceClient) func(c *cli.Context) error { return func(c *cli.Context) error { ctx, cancel := context.WithCancel(c.Context) + defer cancel() + response, err := client.Dashboard(ctx, &empty.Empty{}) if err != nil { return err } - defer cancel() - box := tui.NewVBox() ui, err := tui.New(tui.NewHBox(box, tui.NewSpacer())) if err != nil { @@ -337,12 +341,11 @@ func netInfoCMD(client pb.ManagerServiceClient) func(c *cli.Context) error { return err } if c.Bool("json") { - bb := new(bytes.Buffer) - err := (&jsonpb.Marshaler{EmitDefaults: true}).Marshal(bb, response) + bb, err := protojson.Marshal(response) if err != nil { return err } - fmt.Println(string(bb.Bytes())) + fmt.Println(string(bb)) return nil } fmt.Println(proto.MarshalTextString(response)) @@ -357,12 +360,11 @@ func statusCMD(client pb.ManagerServiceClient) func(c *cli.Context) error { return err } if c.Bool("json") { - bb := new(bytes.Buffer) - err := (&jsonpb.Marshaler{EmitDefaults: true}).Marshal(bb, response) + bb, err := protojson.Marshal(response) if err != nil { return err } - fmt.Println(string(bb.Bytes())) + fmt.Println(string(bb)) return nil } fmt.Println(proto.MarshalTextString(response)) @@ -372,15 +374,61 @@ func statusCMD(client pb.ManagerServiceClient) func(c *cli.Context) error { func pruneBlocksCMD(client pb.ManagerServiceClient) func(c *cli.Context) error { return func(c *cli.Context) error { - _, err := client.PruneBlocks(c.Context, &pb.PruneBlocksRequest{ + ctx, cancel := context.WithCancel(c.Context) + defer cancel() + + stream, err := client.PruneBlocks(ctx, &pb.PruneBlocksRequest{ FromHeight: c.Int64("from"), ToHeight: c.Int64("to"), }) if err != nil { return err } - fmt.Println("OK") - return nil + + errCh := make(chan error) + recvCh := make(chan *pb.PruneBlocksResponse) + + go func() { + for { + select { + case <-ctx.Done(): + return + default: + recv, err := stream.Recv() + if err == io.EOF { + close(errCh) + return + } + if err != nil { + errCh <- err + return + } + recvCh <- recv + } + } + }() + + for { + select { + case <-c.Done(): + return c.Err() + case err, more := <-errCh: + _ = stream.CloseSend() + if more { + close(errCh) + return err + } + fmt.Println("OK") + return nil + case recv := <-recvCh: + var percent int64 + if recv.Total != 0 { + percent = int64(float64(recv.Current) / float64(recv.Total) * 100.0) + } + log.Println() + fmt.Printf("%d%% successfully removed (%d of %d)\n", percent, recv.Current, recv.Total) + } + } } } diff --git a/cli/service/server.go b/cli/service/server.go index 7103875bc..ba634fbd2 100644 --- a/cli/service/server.go +++ b/cli/service/server.go @@ -2,7 +2,9 @@ package service import ( "context" - "github.com/MinterTeam/minter-go-node/cli/pb" + pb "github.com/MinterTeam/minter-go-node/cli/cli_pb" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" "google.golang.org/grpc" "net" "os" @@ -18,7 +20,14 @@ func StartCLIServer(socketPath string, manager pb.ManagerServiceServer, ctx cont return err } - server := grpc.NewServer() + server := grpc.NewServer( + grpc_middleware.WithStreamServerChain( + grpc_recovery.StreamServerInterceptor(), + ), + grpc_middleware.WithUnaryServerChain( + grpc_recovery.UnaryServerInterceptor(), + ), + ) pb.RegisterManagerServiceServer(server, manager) @@ -30,7 +39,6 @@ func StartCLIServer(socketPath string, manager pb.ManagerServiceServer, ctx cont server.GracefulStop() case <-kill: } - return }() if err := server.Serve(lis); err != nil { diff --git a/cli/service/server_test.go b/cli/service/server_test.go index 42f0b7536..63881da4d 100644 --- a/cli/service/server_test.go +++ b/cli/service/server_test.go @@ -4,7 +4,8 @@ import ( "context" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/minter" - rpc "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/node" + rpc "github.com/tendermint/tendermint/rpc/client/local" "io/ioutil" "path/filepath" "testing" @@ -15,19 +16,20 @@ func TestStartCLIServer(t *testing.T) { var ( blockchain *minter.Blockchain tmRPC *rpc.Local + tmNode *node.Node cfg *config.Config ) ctx, cancel := context.WithCancel(context.Background()) socketPath, _ := filepath.Abs(filepath.Join(".", "file.sock")) _ = ioutil.WriteFile(socketPath, []byte("address already in use"), 0644) go func() { - err := StartCLIServer(socketPath, NewManager(blockchain, tmRPC, cfg), ctx) + err := StartCLIServer(socketPath, NewManager(blockchain, tmRPC, tmNode, cfg), ctx) if err != nil { t.Log(err) } }() time.Sleep(time.Millisecond) - console, err := ConfigureManagerConsole(socketPath) + console, err := NewCLI(socketPath) if err != nil { t.Log(err) } diff --git a/cli/service/service.go b/cli/service/service.go index e2f34c50d..18d13e02d 100644 --- a/cli/service/service.go +++ b/cli/service/service.go @@ -3,14 +3,18 @@ package service import ( "context" "fmt" - "github.com/MinterTeam/minter-go-node/cli/pb" + pb "github.com/MinterTeam/minter-go-node/cli/cli_pb" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/minter" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/version" "github.com/golang/protobuf/ptypes" "github.com/golang/protobuf/ptypes/empty" - rpc "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/evidence" + tmNode "github.com/tendermint/tendermint/node" + "github.com/tendermint/tendermint/p2p" + rpc "github.com/tendermint/tendermint/rpc/client/local" + typesTM "github.com/tendermint/tendermint/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "runtime" @@ -20,11 +24,12 @@ import ( type Manager struct { blockchain *minter.Blockchain tmRPC *rpc.Local + tmNode *tmNode.Node cfg *config.Config } -func NewManager(blockchain *minter.Blockchain, tmRPC *rpc.Local, cfg *config.Config) pb.ManagerServiceServer { - return &Manager{blockchain: blockchain, tmRPC: tmRPC, cfg: cfg} +func NewManager(blockchain *minter.Blockchain, tmRPC *rpc.Local, tmNode *tmNode.Node, cfg *config.Config) pb.ManagerServiceServer { + return &Manager{blockchain: blockchain, tmRPC: tmRPC, tmNode: tmNode, cfg: cfg} } func (m *Manager) Dashboard(_ *empty.Empty, stream pb.ManagerService_DashboardServer) error { @@ -41,8 +46,12 @@ func (m *Manager) Dashboard(_ *empty.Empty, stream pb.ManagerService_DashboardSe info := statisticData.GetLastBlockInfo() averageTimeBlock := statisticData.GetAverageBlockProcessingTime() timePerBlock := statisticData.GetTimePerBlock() - maxPeersHeight := m.blockchain.MaxPeerHeight() - maxPeersHeight = maxPeersHeight - 1 + maxPeersHeight := maxPeerHeight(m.tmNode.Switch()) + if maxPeersHeight == 0 { + maxPeersHeight = info.Height + } else { + maxPeersHeight = maxPeersHeight - 1 + } protoTime, _ := ptypes.TimestampProto(info.HeaderTimestamp) var mem runtime.MemStats runtime.ReadMemStats(&mem) @@ -63,7 +72,7 @@ func (m *Manager) Dashboard(_ *empty.Empty, stream pb.ManagerService_DashboardSe var address types.TmAddress copy(address[:], resultStatus.ValidatorInfo.Address) - validator := state.Validators.GetByTmAddress(address) + validator := state.Validators().GetByTmAddress(address) validatorStatus := m.blockchain.GetValidatorStatus(address) var pbValidatorStatus pb.DashboardResponse_ValidatorStatus @@ -108,52 +117,21 @@ func (m *Manager) Dashboard(_ *empty.Empty, stream pb.ManagerService_DashboardSe } func (m *Manager) Status(context.Context, *empty.Empty) (*pb.StatusResponse, error) { - resultStatus, err := m.tmRPC.Status() + result, err := m.tmRPC.Status() if err != nil { return new(pb.StatusResponse), status.Error(codes.Internal, err.Error()) } response := &pb.StatusResponse{ Version: version.Version, - LatestBlockHash: fmt.Sprintf("%X", resultStatus.SyncInfo.LatestBlockHash), - LatestAppHash: fmt.Sprintf("%X", resultStatus.SyncInfo.LatestAppHash), - LatestBlockHeight: resultStatus.SyncInfo.LatestBlockHeight, - LatestBlockTime: resultStatus.SyncInfo.LatestBlockTime.Format(time.RFC3339), - KeepLastStates: m.cfg.KeepLastStates, - TmStatus: &pb.StatusResponse_TmStatus{ - NodeInfo: &pb.NodeInfo{ - ProtocolVersion: &pb.NodeInfo_ProtocolVersion{ - P2P: uint64(resultStatus.NodeInfo.ProtocolVersion.P2P), - Block: uint64(resultStatus.NodeInfo.ProtocolVersion.Block), - App: uint64(resultStatus.NodeInfo.ProtocolVersion.App), - }, - Id: fmt.Sprintf("%X", resultStatus.NodeInfo.ID()), - ListenAddr: resultStatus.NodeInfo.ListenAddr, - Network: resultStatus.NodeInfo.Network, - Version: resultStatus.NodeInfo.Version, - Channels: fmt.Sprintf("%X", resultStatus.NodeInfo.Channels), - Moniker: resultStatus.NodeInfo.Moniker, - Other: &pb.NodeInfo_Other{ - TxIndex: resultStatus.NodeInfo.Other.TxIndex, - RpcAddress: resultStatus.NodeInfo.Other.RPCAddress, - }, - }, - SyncInfo: &pb.StatusResponse_TmStatus_SyncInfo{ - LatestBlockHash: fmt.Sprintf("%X", resultStatus.SyncInfo.LatestBlockHash), - LatestAppHash: fmt.Sprintf("%X", resultStatus.SyncInfo.LatestAppHash), - LatestBlockHeight: resultStatus.SyncInfo.LatestBlockHeight, - LatestBlockTime: resultStatus.SyncInfo.LatestBlockTime.Format(time.RFC3339), - CatchingUp: resultStatus.SyncInfo.CatchingUp, - }, - ValidatorInfo: &pb.StatusResponse_TmStatus_ValidatorInfo{ - Address: fmt.Sprintf("%X", resultStatus.ValidatorInfo.Address), - PubKey: &pb.StatusResponse_TmStatus_ValidatorInfo_PubKey{ - Type: "tendermint/PubKeyEd25519", - Value: fmt.Sprintf("%X", resultStatus.ValidatorInfo.PubKey.Bytes()), - }, - VotingPower: resultStatus.ValidatorInfo.VotingPower, - }, - }, + LatestBlockHash: fmt.Sprintf("%X", result.SyncInfo.LatestBlockHash), + LatestAppHash: fmt.Sprintf("%X", result.SyncInfo.LatestAppHash), + LatestBlockHeight: fmt.Sprintf("%d", result.SyncInfo.LatestBlockHeight), + LatestBlockTime: result.SyncInfo.LatestBlockTime.Format(time.RFC3339Nano), + KeepLastStates: fmt.Sprintf("%d", m.cfg.BaseConfig.KeepLastStates), + CatchingUp: result.SyncInfo.CatchingUp, + PublicKey: fmt.Sprintf("Mp%x", result.ValidatorInfo.PubKey.Bytes()[5:]), + NodeId: string(result.NodeInfo.ID()), } return response, nil @@ -170,14 +148,16 @@ func (m *Manager) NetInfo(context.Context, *empty.Empty) (*pb.NetInfoResponse, e channels := make([]*pb.NetInfoResponse_Peer_ConnectionStatus_Channel, 0, len(peer.ConnectionStatus.Channels)) for _, channel := range peer.ConnectionStatus.Channels { channels = append(channels, &pb.NetInfoResponse_Peer_ConnectionStatus_Channel{ - ID: int32(channel.ID), + Id: int32(channel.ID), SendQueueCapacity: int64(channel.SendQueueCapacity), SendQueueSize: int64(channel.SendQueueSize), Priority: int64(channel.Priority), RecentlySent: channel.RecentlySent, }) } + peerHeight := peerHeight(m.tmNode.Switch(), peer.NodeInfo.ID()) peers = append(peers, &pb.NetInfoResponse_Peer{ + LatestBlockHeight: peerHeight, NodeInfo: &pb.NodeInfo{ ProtocolVersion: &pb.NodeInfo_ProtocolVersion{ P2P: uint64(peer.NodeInfo.ProtocolVersion.P2P), @@ -235,20 +215,64 @@ func (m *Manager) NetInfo(context.Context, *empty.Empty) (*pb.NetInfoResponse, e } response := &pb.NetInfoResponse{ - Listening: resultNetInfo.Listening, - Listeners: resultNetInfo.Listeners, - NPeers: int64(resultNetInfo.NPeers), - Peers: peers, + Listening: resultNetInfo.Listening, + Listeners: resultNetInfo.Listeners, + CountPeers: int64(resultNetInfo.NPeers), + Peers: peers, } return response, nil } -func (m *Manager) PruneBlocks(ctx context.Context, req *pb.PruneBlocksRequest) (*empty.Empty, error) { - return new(empty.Empty), status.Error(codes.Unimplemented, "todo") +const countBatchBlocksDelete = 250 + +func (m *Manager) PruneBlocks(req *pb.PruneBlocksRequest, stream pb.ManagerService_PruneBlocksServer) error { + current := m.blockchain.Height() + if req.ToHeight >= int64(current) { + return status.Errorf(codes.FailedPrecondition, "cannot delete latest saved version (%d)", current) + } + + min := req.FromHeight + total := req.ToHeight - min + + from := req.FromHeight + last := make(chan struct{}) + + for i := req.FromHeight; i <= req.ToHeight; i++ { + if i == req.ToHeight { + close(last) + } + select { + case <-stream.Context().Done(): + return status.Error(codes.Canceled, stream.Context().Err().Error()) + case <-last: + _ = m.blockchain.DeleteStateVersions(from, i) + if err := stream.Send(&pb.PruneBlocksResponse{ + Total: total, + Current: i - min, + }); err != nil { + return err + } + return nil + default: + if i-from != countBatchBlocksDelete { + continue + } + _ = m.blockchain.DeleteStateVersions(from, i) + if err := stream.Send(&pb.PruneBlocksResponse{ + Total: total, + Current: i - min, + }); err != nil { + return err + } + from = i + } + } + + return nil } -func (m *Manager) DealPeer(ctx context.Context, req *pb.DealPeerRequest) (*empty.Empty, error) { +func (m *Manager) DealPeer(_ context.Context, req *pb.DealPeerRequest) (*empty.Empty, error) { res := new(empty.Empty) _, err := m.tmRPC.DialPeers([]string{req.Address}, req.Persistent) if err != nil { @@ -256,3 +280,34 @@ func (m *Manager) DealPeer(ctx context.Context, req *pb.DealPeerRequest) (*empty } return res, nil } + +func maxPeerHeight(sw *p2p.Switch) int64 { + var max int64 + for _, peer := range sw.Peers().List() { + peerState, ok := peer.Get(typesTM.PeerStateKey).(evidence.PeerState) + if !ok { + continue + } + height := peerState.GetHeight() + if height > max { + max = height + } + } + return max +} + +func peerHeight(sw *p2p.Switch, id p2p.ID) int64 { + peerTM := sw.Peers().Get(id) + if peerTM == nil { + return 0 + } + ps := peerTM.Get(typesTM.PeerStateKey) + if ps == nil { + return 0 + } + peerState, ok := ps.(evidence.PeerState) + if !ok { + return 0 + } + return peerState.GetHeight() +} diff --git a/cmd/minter/cmd/export.go b/cmd/minter/cmd/export.go index 6d239c961..fb86f18c2 100644 --- a/cmd/minter/cmd/export.go +++ b/cmd/minter/cmd/export.go @@ -41,16 +41,26 @@ func export(cmd *cobra.Command, args []string) error { log.Panicf("Cannot parse height: %s", err) } - chainID, err := cmd.Flags().GetString("chain_id") + startHeight, err := cmd.Flags().GetUint64("start-height") + if err != nil { + log.Panicf("Cannot parse start-height: %s", err) + } + + chainID, err := cmd.Flags().GetString("chain-id") if err != nil { log.Panicf("Cannot parse chain id: %s", err) } - genesisTime, err := cmd.Flags().GetDuration("genesis_time") + genesisTime, err := cmd.Flags().GetDuration("genesis-time") if err != nil { log.Panicf("Cannot parse genesis time: %s", err) } + indent, err := cmd.Flags().GetBool("indent") + if err != nil { + log.Panicf("Cannot parse indent: %s", err) + } + fmt.Println("Start exporting...") ldb, err := db.NewGoLevelDB("state", utils.GetMinterHome()+"/data") @@ -58,15 +68,24 @@ func export(cmd *cobra.Command, args []string) error { log.Panicf("Cannot load db: %s", err) } - currentState, err := state.NewState(height, ldb, nil, 1, 1) + currentState, err := state.NewCheckStateAtHeight(height, ldb) if err != nil { log.Panicf("Cannot new state at given height: %s", err) } - exportTimeStart, newState := time.Now(), currentState.Export(height) + exportTimeStart, newState := time.Now(), currentState.Export11To12(height) fmt.Printf("State has been exported. Took %s", time.Since(exportTimeStart)) - jsonBytes, err := amino.NewCodec().MarshalJSONIndent(newState, "", " ") + if startHeight > 0 { + newState.StartHeight = startHeight + } + + var jsonBytes []byte + if indent { + jsonBytes, err = amino.NewCodec().MarshalJSONIndent(newState, "", " ") + } else { + jsonBytes, err = amino.NewCodec().MarshalJSON(newState) + } if err != nil { log.Panicf("Cannot marshal state to json: %s", err) } diff --git a/cmd/minter/cmd/manager.go b/cmd/minter/cmd/manager.go index 246932944..28269d824 100644 --- a/cmd/minter/cmd/manager.go +++ b/cmd/minter/cmd/manager.go @@ -13,7 +13,7 @@ var ManagerCommand = &cobra.Command{ DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { newArgs := setParentFlags(cmd, args) - console, err := service.ConfigureManagerConsole(utils.GetMinterHome() + "/manager.sock") + console, err := service.NewCLI(utils.GetMinterHome() + "/manager.sock") if err != nil { return nil } @@ -27,7 +27,7 @@ var ManagerConsole = &cobra.Command{ DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { _ = setParentFlags(cmd, args) - console, err := service.ConfigureManagerConsole(utils.GetMinterHome() + "/manager.sock") + console, err := service.NewCLI(utils.GetMinterHome() + "/manager.sock") if err != nil { return nil } diff --git a/cmd/minter/cmd/node.go b/cmd/minter/cmd/node.go index bfa87bf51..9847f7a0f 100644 --- a/cmd/minter/cmd/node.go +++ b/cmd/minter/cmd/node.go @@ -2,12 +2,13 @@ package cmd import ( "fmt" - api_v1 "github.com/MinterTeam/minter-go-node/api" - api_v2 "github.com/MinterTeam/minter-go-node/api/v2" - service_api "github.com/MinterTeam/minter-go-node/api/v2/service" + apiV1 "github.com/MinterTeam/minter-go-node/api" + apiV2 "github.com/MinterTeam/minter-go-node/api/v2" + serviceApi "github.com/MinterTeam/minter-go-node/api/v2/service" "github.com/MinterTeam/minter-go-node/cli/service" "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/config" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/minter" "github.com/MinterTeam/minter-go-node/core/statistics" "github.com/MinterTeam/minter-go-node/log" @@ -16,25 +17,29 @@ import ( "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/abci/types" tmCfg "github.com/tendermint/tendermint/config" - tmlog "github.com/tendermint/tendermint/libs/log" - tmos "github.com/tendermint/tendermint/libs/os" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/multisig" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/evidence" + tmLog "github.com/tendermint/tendermint/libs/log" + tmOS "github.com/tendermint/tendermint/libs/os" tmNode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" - rpc "github.com/tendermint/tendermint/rpc/client" + rpc "github.com/tendermint/tendermint/rpc/client/local" "github.com/tendermint/tendermint/store" tmTypes "github.com/tendermint/tendermint/types" "io" "net/http" - _ "net/http/pprof" + _ "net/http/pprof" // nolint: gosec // securely exposed on separate, optional port "net/url" "os" "syscall" ) -const RequiredOpenFilesLimit = 10000 - +// RunNode is the command that allows the CLI to start a node. var RunNode = &cobra.Command{ Use: "node", Short: "Run the Minter node", @@ -47,21 +52,13 @@ func runNode(cmd *cobra.Command) error { logger := log.NewLogger(cfg) // check open files limits - { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - panic(err) - } + if err := checkRlimits(); err != nil { + panic(err) + } - required := RequiredOpenFilesLimit + uint64(cfg.StateMemAvailable) - if rLimit.Cur < required { - rLimit.Cur = required - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - panic(fmt.Errorf("cannot set RLIMIT_NOFILE to %d", rLimit.Cur)) - } - } + // ensure /config and /tmdata dirs + if err := ensureDirs(); err != nil { + return err } pprofOn, err := cmd.Flags().GetBool("pprof") @@ -70,35 +67,13 @@ func runNode(cmd *cobra.Command) error { } if pprofOn { - pprofAddr, err := cmd.Flags().GetString("pprof-addr") - if err != nil { + if err := enablePprof(cmd, logger); err != nil { return err } - - pprofMux := http.DefaultServeMux - http.DefaultServeMux = http.NewServeMux() - go func() { - logger.Error((&http.Server{ - Addr: pprofAddr, - Handler: pprofMux, - }).ListenAndServe().Error()) - }() } tmConfig := config.GetTmConfig(cfg) - if err := tmos.EnsureDir(utils.GetMinterHome()+"/config", 0777); err != nil { - return err - } - - if err := tmos.EnsureDir(utils.GetMinterHome()+"/tmdata", 0777); err != nil { - return err - } - - if cfg.KeepLastStates < 1 { - panic("keep_last_states field should be greater than 0") - } - app := minter.NewMinterBlockchain(cfg) // update BlocksTimeDelta in case it was corrupted @@ -106,49 +81,135 @@ func runNode(cmd *cobra.Command) error { // start TM node node := startTendermintNode(app, tmConfig, logger) - - client := rpc.NewLocal(node) - + client := rpc.New(node) app.SetTmNode(node) if !cfg.ValidatorMode { - go func(srv *service_api.Service) { - grpcUrl, err := url.Parse(cfg.GRPCListenAddress) - if err != nil { - logger.Error("Failed to parse gRPC address", err) - } - apiV2url, err := url.Parse(cfg.APIv2ListenAddress) - if err != nil { - logger.Error("Failed to parse API v2 address", err) - } - logger.Error("Failed to start Api V2 in both gRPC and RESTful", api_v2.Run(srv, grpcUrl.Host, apiV2url.Host)) - }(service_api.NewService(amino.NewCodec(), app, client, node, cfg, version.Version)) + runAPI(logger, app, client, node) + } + + runCLI(cmd, app, client, node) + + if cfg.Instrumentation.Prometheus { + go app.SetStatisticData(statistics.New()).Statistic(cmd.Context()) + } - go api_v1.RunAPI(app, client, cfg, logger) + <-cmd.Context().Done() + + defer app.Stop() + if err := node.Stop(); err != nil { + return err } + return nil +} + +func runCLI(cmd *cobra.Command, app *minter.Blockchain, client *rpc.Local, tmNode *tmNode.Node) { go func() { - err := service.StartCLIServer(utils.GetMinterHome()+"/manager.sock", service.NewManager(app, client, cfg), cmd.Context()) + err := service.StartCLIServer(utils.GetMinterHome()+"/manager.sock", service.NewManager(app, client, tmNode, cfg), cmd.Context()) if err != nil { panic(err) } }() +} - if cfg.Instrumentation.Prometheus { - data := statistics.New() - go app.SetStatisticData(data).Statistic(cmd.Context()) +// RegisterAmino registers all crypto related types in the given (amino) codec. +func registerCryptoAmino(cdc *amino.Codec) { + // These are all written here instead of + cdc.RegisterInterface((*crypto.PubKey)(nil), nil) + cdc.RegisterConcrete(ed25519.PubKeyEd25519{}, + ed25519.PubKeyAminoName, nil) + cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, + secp256k1.PubKeyAminoName, nil) + cdc.RegisterConcrete(multisig.PubKeyMultisigThreshold{}, + multisig.PubKeyMultisigThresholdAminoRoute, nil) + + cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) + cdc.RegisterConcrete(ed25519.PrivKeyEd25519{}, + ed25519.PrivKeyAminoName, nil) + cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{}, + secp256k1.PrivKeyAminoName, nil) +} + +func registerEvidenceMessages(cdc *amino.Codec) { + cdc.RegisterInterface((*evidence.Message)(nil), nil) + cdc.RegisterConcrete(&evidence.ListMessage{}, + "tendermint/evidence/EvidenceListMessage", nil) + cdc.RegisterInterface((*tmTypes.Evidence)(nil), nil) + cdc.RegisterConcrete(&tmTypes.DuplicateVoteEvidence{}, "tendermint/DuplicateVoteEvidence", nil) +} + +func runAPI(logger tmLog.Logger, app *minter.Blockchain, client *rpc.Local, node *tmNode.Node) { + cdc := amino.NewCodec() + registerCryptoAmino(cdc) + eventsdb.RegisterAminoEvents(cdc) + registerEvidenceMessages(cdc) + go func(srv *serviceApi.Service) { + grpcURL, err := url.Parse(cfg.GRPCListenAddress) + if err != nil { + logger.Error("Failed to parse gRPC address", err) + } + apiV2url, err := url.Parse(cfg.APIv2ListenAddress) + if err != nil { + logger.Error("Failed to parse API v2 address", err) + } + logger.Error("Failed to start Api V2 in both gRPC and RESTful", + apiV2.Run(srv, grpcURL.Host, apiV2url.Host, logger.With("module", "rpc"))) + }(serviceApi.NewService(cdc, app, client, node, cfg, version.Version)) + + go apiV1.RunAPI(cdc, app, client, cfg, logger) +} + +func enablePprof(cmd *cobra.Command, logger tmLog.Logger) error { + pprofAddr, err := cmd.Flags().GetString("pprof-addr") + if err != nil { + return err } - <-cmd.Context().Done() + pprofMux := http.DefaultServeMux + http.DefaultServeMux = http.NewServeMux() + go func() { + logger.Error((&http.Server{ + Addr: pprofAddr, + Handler: pprofMux, + }).ListenAndServe().Error()) + }() + return nil +} - defer app.Stop() - if err := node.Stop(); err != nil { +func ensureDirs() error { + if err := tmOS.EnsureDir(utils.GetMinterHome()+"/config", 0777); err != nil { + return err + } + + if err := tmOS.EnsureDir(utils.GetMinterHome()+"/tmdata", 0777); err != nil { return err } return nil } +func checkRlimits() error { + const RequiredOpenFilesLimit = 10000 + + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return err + } + + required := RequiredOpenFilesLimit + uint64(cfg.StateMemAvailable) + if rLimit.Cur < required { + rLimit.Cur = required + err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return fmt.Errorf("cannot set RLIMIT_NOFILE to %d", rLimit.Cur) + } + } + + return nil +} + func updateBlocksTimeDelta(app *minter.Blockchain, config *tmCfg.Config) { blockStoreDB, err := tmNode.DefaultDBProvider(&tmNode.DBContext{ID: "blockstore", Config: config}) if err != nil { @@ -168,7 +229,7 @@ func updateBlocksTimeDelta(app *minter.Blockchain, config *tmCfg.Config) { blockStoreDB.Close() } -func startTendermintNode(app types.Application, cfg *tmCfg.Config, logger tmlog.Logger) *tmNode.Node { +func startTendermintNode(app types.Application, cfg *tmCfg.Config, logger tmLog.Logger) *tmNode.Node { nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) if err != nil { panic(err) @@ -205,17 +266,22 @@ func getGenesis() (doc *tmTypes.GenesisDoc, e error) { _, err := os.Stat(genDocFile) if err != nil { if !os.IsNotExist(err) { - panic(err) + return nil, err } - if err := downloadFile(genDocFile, "https://raw.githubusercontent.com/MinterTeam/minter-network-migrate/master/minter-mainnet-2/genesis.json"); err != nil { - panic(err) + + genesis, err := RootCmd.Flags().GetString("genesis") + if err != nil { + return nil, err + } + + if err := downloadFile(genDocFile, genesis); err != nil { + return nil, err } } return tmTypes.GenesisDocFromFile(genDocFile) } func downloadFile(filepath string, url string) error { - // Get the data resp, err := http.Get(url) if err != nil { diff --git a/cmd/minter/cmd/root.go b/cmd/minter/cmd/root.go index 1f72e3e5e..4a75ea4c3 100644 --- a/cmd/minter/cmd/root.go +++ b/cmd/minter/cmd/root.go @@ -3,6 +3,8 @@ package cmd import ( "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/config" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/version" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -24,5 +26,15 @@ var RootCmd = &cobra.Command{ if err := v.Unmarshal(cfg); err != nil { panic(err) } + + if cfg.KeepLastStates < 1 { + panic("keep_last_states field should be greater than 0") + } + + isTestnet, _ := cmd.Flags().GetBool("testnet") + if isTestnet { + types.CurrentChainID = types.ChainTestnet + version.Version += "-testnet" + } }, } diff --git a/cmd/minter/cmd/show_validator.go b/cmd/minter/cmd/show_validator.go index ada67b4bc..0b2c4e3d8 100644 --- a/cmd/minter/cmd/show_validator.go +++ b/cmd/minter/cmd/show_validator.go @@ -29,6 +29,10 @@ func showValidator(cmd *cobra.Command, args []string) error { } pv := privval.LoadFilePV(keyFilePath, cfg.PrivValidatorStateFile()) - fmt.Printf("Mp%x\n", pv.GetPubKey().Bytes()[5:]) + key, err := pv.GetPubKey() + if err != nil { + panic(err) + } + fmt.Printf("Mp%x\n", key.Bytes()[5:]) return nil } diff --git a/cmd/minter/main.go b/cmd/minter/main.go index fcae7166f..9b8c6a997 100644 --- a/cmd/minter/main.go +++ b/cmd/minter/main.go @@ -31,12 +31,16 @@ func main() { rootCmd.PersistentFlags().StringVar(&utils.MinterHome, "home-dir", "", "base dir (default is $HOME/.minter)") rootCmd.PersistentFlags().StringVar(&utils.MinterConfig, "config", "", "path to config (default is $(home-dir)/config/config.toml)") + rootCmd.PersistentFlags().Bool("testnet", false, "use \"true\" for testnet, mainnet is default") rootCmd.PersistentFlags().Bool("pprof", false, "enable pprof") rootCmd.PersistentFlags().String("pprof-addr", "0.0.0.0:6060", "pprof listen addr") + rootCmd.PersistentFlags().String("genesis", "https://raw.githubusercontent.com/MinterTeam/minter-network-migrate/master/minter-mainnet-2/genesis.json", "path with the genesis file to download") cmd.ExportCommand.Flags().Uint64("height", 0, "export height") - cmd.ExportCommand.Flags().String("chain_id", "", "export chain id") - cmd.ExportCommand.Flags().Duration("genesis_time", 0, "export height") + cmd.ExportCommand.Flags().Uint64("start-height", 0, "height for starting a new chain") + cmd.ExportCommand.Flags().Bool("indent", false, "using indent") + cmd.ExportCommand.Flags().String("chain-id", "", "export chain id") + cmd.ExportCommand.Flags().Duration("genesis-time", 0, "export height") if err := rootCmd.ExecuteContext(ctx); err != nil { panic(err) diff --git a/config/config.go b/config/config.go index b2c775322..b142ce6ae 100644 --- a/config/config.go +++ b/config/config.go @@ -34,6 +34,7 @@ var ( defaultNodeKeyPath = filepath.Join(defaultConfigDir, defaultNodeKeyName) ) +// DefaultConfig returns config with predefined values func DefaultConfig() *Config { cfg := defaultConfig() @@ -74,6 +75,7 @@ func DefaultConfig() *Config { return cfg } +// GetConfig returns DefaultConfig with some changes func GetConfig() *Config { cfg := DefaultConfig() @@ -132,6 +134,7 @@ func (cfg *Config) SetRoot(root string) *Config { return cfg } +// GetTmConfig composes and returns config for Tendermint engine based on given Minter config func GetTmConfig(cfg *Config) *tmConfig.Config { return &tmConfig.Config{ BaseConfig: tmConfig.BaseConfig{ @@ -233,6 +236,9 @@ type BaseConfig struct { // Address to listen for API v2 connections APIv2ListenAddress string `mapstructure:"api_v2_listen_addr"` + // API v2 Timeout + APIv2TimeoutDuration time.Duration `mapstructure:"api_v2_timeout_duration"` + ValidatorMode bool `mapstructure:"validator_mode"` KeepLastStates int64 `mapstructure:"keep_last_states"` @@ -265,6 +271,7 @@ func DefaultBaseConfig() BaseConfig { APIListenAddress: "tcp://0.0.0.0:8841", GRPCListenAddress: "tcp://0.0.0.0:8842", APIv2ListenAddress: "tcp://0.0.0.0:8843", + APIv2TimeoutDuration: 10 * time.Second, ValidatorMode: false, KeepLastStates: 120, StateCacheSize: 1000000, @@ -275,6 +282,7 @@ func DefaultBaseConfig() BaseConfig { } } +// ChainID returns the id of a chain func (cfg BaseConfig) ChainID() string { return cfg.chainID } @@ -284,7 +292,7 @@ func (cfg BaseConfig) GenesisFile() string { return rootify(cfg.Genesis, cfg.RootDir) } -// PrivValidatorFile returns the full path to the priv_validator.json file +// PrivValidatorStateFile returns the full path to the priv_validator_state.json file func (cfg BaseConfig) PrivValidatorStateFile() string { return rootify(cfg.PrivValidatorState, cfg.RootDir) } @@ -294,6 +302,7 @@ func (cfg BaseConfig) NodeKeyFile() string { return rootify(cfg.NodeKey, cfg.RootDir) } +// PrivValidatorKeyFile returns the full path to the priv_validator.json file func (cfg BaseConfig) PrivValidatorKeyFile() string { return rootify(cfg.PrivValidatorKey, cfg.RootDir) } diff --git a/config/toml.go b/config/toml.go index 4d2ec5ecd..b38076ab3 100644 --- a/config/toml.go +++ b/config/toml.go @@ -58,7 +58,7 @@ func WriteConfigFile(configFilePath string, config *Config) { // Note: any changes to the comments/variables/mapstructure // must be reflected in the appropriate struct in config/config.go -const defaultConfigTemplate = `# This is a TOML config file. +const defaultConfigTemplate string = `# This is a TOML config file. # For more information, see https://github.com/toml-lang/toml ##### main base config options ##### @@ -75,10 +75,13 @@ grpc_listen_addr = "{{ .BaseConfig.GRPCListenAddress }}" # Address to listen for API V2 connections api_v2_listen_addr = "{{ .BaseConfig.APIv2ListenAddress }}" +# API v2 Timeout +api_v2_timeout_duration = "{{ .BaseConfig.APIv2TimeoutDuration }}" + # Sets node to be in validator mode. Disables API, events, history of blocks, indexes, etc. validator_mode = {{ .BaseConfig.ValidatorMode }} -# Sets number of last stated to be saved +# Sets number of last stated to be saved on disk. keep_last_states = {{ .BaseConfig.KeepLastStates }} # State cache size diff --git a/core/appdb/appdb.go b/core/appdb/appdb.go index f5a177a97..a764a86b9 100644 --- a/core/appdb/appdb.go +++ b/core/appdb/appdb.go @@ -24,14 +24,19 @@ const ( dbName = "app" ) +// AppDB is responsible for storing basic information about app state on disk type AppDB struct { db db.DB } +// Close closes db connection, panics on error func (appDB *AppDB) Close() { - appDB.db.Close() + if err := appDB.db.Close(); err != nil { + panic(err) + } } +// GetLastBlockHash returns latest block hash stored on disk func (appDB *AppDB) GetLastBlockHash() []byte { var hash [32]byte @@ -44,10 +49,14 @@ func (appDB *AppDB) GetLastBlockHash() []byte { return hash[:] } +// SetLastBlockHash stores given block hash on disk, panics on error func (appDB *AppDB) SetLastBlockHash(hash []byte) { - appDB.db.Set([]byte(hashPath), hash) + if err := appDB.db.Set([]byte(hashPath), hash); err != nil { + panic(err) + } } +// GetLastHeight returns latest block height stored on disk func (appDB *AppDB) GetLastHeight() uint64 { result, err := appDB.db.Get([]byte(heightPath)) if err != nil { @@ -62,18 +71,25 @@ func (appDB *AppDB) GetLastHeight() uint64 { return height } +// SetLastHeight stores given block height on disk, panics on error func (appDB *AppDB) SetLastHeight(height uint64) { h := make([]byte, 8) binary.BigEndian.PutUint64(h, height) - appDB.db.Set([]byte(heightPath), h) + if err := appDB.db.Set([]byte(heightPath), h); err != nil { + panic(err) + } } +// SetStartHeight stores given block height on disk as start height, panics on error func (appDB *AppDB) SetStartHeight(height uint64) { h := make([]byte, 8) binary.BigEndian.PutUint64(h, height) - appDB.db.Set([]byte(startHeightPath), h) + if err := appDB.db.Set([]byte(startHeightPath), h); err != nil { + panic(err) + } } +// GetStartHeight returns start height stored on disk func (appDB *AppDB) GetStartHeight() uint64 { result, err := appDB.db.Get([]byte(startHeightPath)) if err != nil { @@ -88,6 +104,7 @@ func (appDB *AppDB) GetStartHeight() uint64 { return height } +// GetValidators returns list of latest validators stored on dist func (appDB *AppDB) GetValidators() types.ValidatorUpdates { result, err := appDB.db.Get([]byte(validatorsPath)) if err != nil { @@ -101,7 +118,6 @@ func (appDB *AppDB) GetValidators() types.ValidatorUpdates { var vals types.ValidatorUpdates err = cdc.UnmarshalBinaryBare(result, &vals) - if err != nil { panic(err) } @@ -109,45 +125,49 @@ func (appDB *AppDB) GetValidators() types.ValidatorUpdates { return vals } +// SaveValidators stores given validators list on disk, panics on error func (appDB *AppDB) SaveValidators(vals types.ValidatorUpdates) { data, err := cdc.MarshalBinaryBare(vals) - if err != nil { panic(err) } - appDB.db.Set([]byte(validatorsPath), data) + if err := appDB.db.Set([]byte(validatorsPath), data); err != nil { + panic(err) + } } -type LastBlocksTimeDelta struct { +type lastBlocksTimeDelta struct { Height uint64 Delta int } +// GetLastBlocksTimeDelta returns delta of time between latest blocks func (appDB *AppDB) GetLastBlocksTimeDelta(height uint64) (int, error) { result, err := appDB.db.Get([]byte(blockTimeDeltaPath)) if err != nil { panic(err) } if result == nil { - return 0, errors.New("no info about LastBlocksTimeDelta is available") + return 0, errors.New("no info about lastBlocksTimeDelta is available") } - data := LastBlocksTimeDelta{} + data := lastBlocksTimeDelta{} err = cdc.UnmarshalBinaryBare(result, &data) if err != nil { panic(err) } if data.Height != height { - return 0, errors.New("no info about LastBlocksTimeDelta is available") + return 0, errors.New("no info about lastBlocksTimeDelta is available") } return data.Delta, nil } +// SetLastBlocksTimeDelta stores delta of time between latest blocks func (appDB *AppDB) SetLastBlocksTimeDelta(height uint64, delta int) { - data, err := cdc.MarshalBinaryBare(LastBlocksTimeDelta{ + data, err := cdc.MarshalBinaryBare(lastBlocksTimeDelta{ Height: height, Delta: delta, }) @@ -156,9 +176,12 @@ func (appDB *AppDB) SetLastBlocksTimeDelta(height uint64, delta int) { panic(err) } - appDB.db.Set([]byte(blockTimeDeltaPath), data) + if err := appDB.db.Set([]byte(blockTimeDeltaPath), data); err != nil { + panic(err) + } } +// NewAppDB creates AppDB instance with given config func NewAppDB(cfg *config.Config) *AppDB { return &AppDB{ db: db.NewDB(dbName, db.BackendType(cfg.DBBackend), utils.GetMinterHome()+"/data"), diff --git a/core/check/check.go b/core/check/check.go index 45de54814..15940ee98 100644 --- a/core/check/check.go +++ b/core/check/check.go @@ -7,32 +7,46 @@ import ( "fmt" "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/rlp" + "golang.org/x/crypto/sha3" "math/big" ) var ( + // ErrInvalidSig represents error on given v, r, s values ErrInvalidSig = errors.New("invalid transaction v, r, s values") ) +// 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. +// +// Nonce - unique "id" of the check. +// Coin Symbol - symbol of coin. +// Value - amount of coins. +// GasCoin - symbol of a coin to pay fee. +// DueBlock - defines last block height in which the check can be used. +// Lock - secret to prevent hijacking. +// V, R, S - signature of issuer. type Check struct { Nonce []byte ChainID types.ChainID DueBlock uint64 - Coin types.CoinSymbol + Coin types.CoinID Value *big.Int - GasCoin types.CoinSymbol + GasCoin types.CoinID Lock *big.Int V *big.Int R *big.Int S *big.Int } +// Sender returns sender's address of a Check, recovered from signature func (check *Check) Sender() (types.Address, error) { return recoverPlain(check.Hash(), check.R, check.S, check.V) } +// LockPubKey returns bytes of public key, which is used for proving check's recipient rights func (check *Check) LockPubKey() ([]byte, error) { sig := check.Lock.Bytes() @@ -53,6 +67,7 @@ func (check *Check) LockPubKey() ([]byte, error) { return pub, nil } +// HashWithoutLock returns a types.Hash to be used in process of signing and checking Lock func (check *Check) HashWithoutLock() types.Hash { return rlpHash([]interface{}{ check.Nonce, @@ -64,6 +79,7 @@ func (check *Check) HashWithoutLock() types.Hash { }) } +// Hash returns a types.Hash to be used in process of signing a Check by sender func (check *Check) Hash() types.Hash { return rlpHash([]interface{}{ check.Nonce, @@ -76,6 +92,7 @@ func (check *Check) Hash() types.Hash { }) } +// Sign signs the check with given private key, returns error func (check *Check) Sign(prv *ecdsa.PrivateKey) error { h := check.Hash() sig, err := crypto.Sign(h[:], prv) @@ -83,12 +100,12 @@ func (check *Check) Sign(prv *ecdsa.PrivateKey) error { return err } - check.SetSignature(sig) + check.setSignature(sig) return nil } -func (check *Check) SetSignature(sig []byte) { +func (check *Check) setSignature(sig []byte) { check.R = new(big.Int).SetBytes(sig[:32]) check.S = new(big.Int).SetBytes(sig[32:64]) check.V = new(big.Int).SetBytes([]byte{sig[64] + 27}) @@ -101,6 +118,7 @@ func (check *Check) String() string { check.DueBlock, check.Value.String(), check.Coin.String()) } +// DecodeFromBytes decodes check from bytes func DecodeFromBytes(buf []byte) (*Check, error) { var check Check err := rlp.Decode(bytes.NewReader(buf), &check) @@ -116,7 +134,7 @@ func DecodeFromBytes(buf []byte) (*Check, error) { } func rlpHash(x interface{}) (h types.Hash) { - hw := sha3.NewKeccak256() + hw := sha3.NewLegacyKeccak256() err := rlp.Encode(hw, x) if err != nil { panic(err) diff --git a/core/code/code.go b/core/code/code.go index 36deba7eb..b1f8fceae 100644 --- a/core/code/code.go +++ b/core/code/code.go @@ -1,5 +1,10 @@ package code +import ( + "strconv" +) + +// Codes for transaction checks and delivers responses const ( // general OK uint32 = 0 @@ -17,6 +22,8 @@ const ( TooLowGasPrice uint32 = 114 WrongChainID uint32 = 115 CoinReserveUnderflow uint32 = 116 + WrongHaltHeight uint32 = 117 + HaltAlreadyExists uint32 = 118 // coin creation CoinAlreadyExists uint32 = 201 @@ -25,6 +32,9 @@ const ( InvalidCoinName uint32 = 204 WrongCoinSupply uint32 = 205 + // recreate coin + IsNotOwnerOfCoin uint32 = 206 + // convert CrossConvert uint32 = 301 MaximumValueToSellReached uint32 = 302 @@ -40,6 +50,9 @@ const ( IncorrectPubKey uint32 = 407 StakeShouldBePositive uint32 = 408 TooLowStake uint32 = 409 + PublicKeyInBlockList uint32 = 410 + NewPublicKeyIsBad uint32 = 411 + InsufficientWaitList uint32 = 412 // check CheckInvalidLock uint32 = 501 @@ -50,10 +63,545 @@ const ( TooLongNonce uint32 = 506 // multisig - IncorrectWeights uint32 = 601 - MultisigExists uint32 = 602 - MultisigNotExists uint32 = 603 - IncorrectMultiSignature uint32 = 604 - TooLargeOwnersList uint32 = 605 - DuplicatedAddresses uint32 = 606 + IncorrectWeights uint32 = 601 + MultisigExists uint32 = 602 + MultisigNotExists uint32 = 603 + IncorrectMultiSignature uint32 = 604 + TooLargeOwnersList uint32 = 605 + DuplicatedAddresses uint32 = 606 + DifferentCountAddressesAndWeights uint32 = 607 + IncorrectTotalWeights uint32 = 608 + NotEnoughMultisigVotes uint32 = 609 ) + +type wrongNonce struct { + Code string `json:"code,omitempty"` + ExpectedNonce string `json:"expected_nonce,omitempty"` + GotNonce string `json:"got_nonce,omitempty"` +} + +func NewWrongNonce(expectedNonce string, gotNonce string) *wrongNonce { + return &wrongNonce{Code: strconv.Itoa(int(WrongNonce)), ExpectedNonce: expectedNonce, GotNonce: gotNonce} +} + +type coinNotExists struct { + Code string `json:"code,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewCoinNotExists(coinSymbol string, coinId string) *coinNotExists { + return &coinNotExists{Code: strconv.Itoa(int(CoinNotExists)), CoinSymbol: coinSymbol, CoinId: coinId} +} + +type wrongGasCoin struct { + Code string `json:"code,omitempty"` + TxGasCoinSymbol string `json:"tx_coin_symbol,omitempty"` + TxGasCoinId string `json:"tx_coin_id,omitempty"` + CheckGasCoinSymbol string `json:"check_coin_symbol,omitempty"` + CheckGasCoinId string `json:"check_coin_id,omitempty"` +} + +func NewWrongGasCoin(txCoinSymbol string, txCoinId string, checkGasCoinSymbol, checkGasCoinId string) *wrongGasCoin { + return &wrongGasCoin{Code: strconv.Itoa(int(WrongGasCoin)), TxGasCoinSymbol: txCoinSymbol, TxGasCoinId: txCoinId, CheckGasCoinId: checkGasCoinId, CheckGasCoinSymbol: checkGasCoinSymbol} +} + +type coinReserveNotSufficient struct { + Code string `json:"code,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` + HasBipValue string `json:"has_bip_value,omitempty"` + RequiredBipValue string `json:"required_bip_value,omitempty"` +} + +func NewCoinReserveNotSufficient(coinSymbol string, coinId string, hasBipValue string, requiredBipValue string) *coinReserveNotSufficient { + return &coinReserveNotSufficient{Code: strconv.Itoa(int(CoinReserveNotSufficient)), CoinSymbol: coinSymbol, CoinId: coinId, HasBipValue: hasBipValue, RequiredBipValue: requiredBipValue} +} + +type txTooLarge struct { + Code string `json:"code,omitempty"` + MaxTxLength string `json:"max_tx_length,omitempty"` + GotTxLength string `json:"got_tx_length,omitempty"` +} + +func NewTxTooLarge(maxTxLength string, gotTxLength string) *txTooLarge { + return &txTooLarge{Code: strconv.Itoa(int(TxTooLarge)), MaxTxLength: maxTxLength, GotTxLength: gotTxLength} +} + +type decodeError struct { + Code string `json:"code,omitempty"` +} + +func NewDecodeError() *decodeError { + return &decodeError{Code: strconv.Itoa(int(DecodeError))} +} + +type insufficientFunds struct { + Code string `json:"code,omitempty"` + Sender string `json:"sender,omitempty"` + NeededBipValue string `json:"needed_bip_value,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewInsufficientFunds(sender string, neededBipValue string, coinSymbol string, coinId string) *insufficientFunds { + return &insufficientFunds{Code: strconv.Itoa(int(InsufficientFunds)), Sender: sender, NeededBipValue: neededBipValue, CoinSymbol: coinSymbol, CoinId: coinId} +} + +type txPayloadTooLarge struct { + Code string `json:"code,omitempty"` + MaxPayloadLength string `json:"max_payload_length,omitempty"` + GotPayloadLength string `json:"got_payload_length,omitempty"` +} + +func NewTxPayloadTooLarge(maxPayloadLength string, gotPayloadLength string) *txPayloadTooLarge { + return &txPayloadTooLarge{Code: strconv.Itoa(int(TxPayloadTooLarge)), MaxPayloadLength: maxPayloadLength, GotPayloadLength: gotPayloadLength} +} + +type txServiceDataTooLarge struct { + Code string `json:"code,omitempty"` + MaxServiceDataLength string `json:"max_service_data_length,omitempty"` + GotServiceDataLength string `json:"got_service_data_length,omitempty"` +} + +func NewTxServiceDataTooLarge(maxServiceDataLength string, gotServiceDataLength string) *txServiceDataTooLarge { + return &txServiceDataTooLarge{Code: strconv.Itoa(int(TxServiceDataTooLarge)), MaxServiceDataLength: maxServiceDataLength, GotServiceDataLength: gotServiceDataLength} +} + +type invalidMultisendData struct { + Code string `json:"code,omitempty"` + MinQuantity string `json:"min_quantity,omitempty"` + MaxQuantity string `json:"max_quantity,omitempty"` + GotQuantity string `json:"got_quantity,omitempty"` +} + +func NewInvalidMultisendData(minQuantity string, maxQuantity string, gotQuantity string) *invalidMultisendData { + return &invalidMultisendData{Code: strconv.Itoa(int(InvalidMultisendData)), MinQuantity: minQuantity, MaxQuantity: maxQuantity, GotQuantity: gotQuantity} +} + +type coinSupplyOverflow struct { + Code string `json:"code,omitempty"` + Delta string `json:"delta,omitempty"` + CoinSupply string `json:"coin_supply,omitempty"` + CurrentSupply string `json:"current_supply,omitempty"` + MaxCoinSupply string `json:"max_coin_supply,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewCoinSupplyOverflow(delta string, coinSupply string, currentSupply string, maxCoinSupply string, coinSymbol string, coinId string) *coinSupplyOverflow { + return &coinSupplyOverflow{Code: strconv.Itoa(int(CoinSupplyOverflow)), Delta: delta, CoinSupply: coinSupply, CurrentSupply: currentSupply, MaxCoinSupply: maxCoinSupply, CoinSymbol: coinSymbol, CoinId: coinId} +} + +type txFromSenderAlreadyInMempool struct { + Code string `json:"code,omitempty"` + Sender string `json:"sender,omitempty"` + BlockHeight string `json:"block_height,omitempty"` +} + +func NewTxFromSenderAlreadyInMempool(sender string, block string) *txFromSenderAlreadyInMempool { + return &txFromSenderAlreadyInMempool{Code: strconv.Itoa(int(TxFromSenderAlreadyInMempool)), Sender: sender, BlockHeight: block} +} + +type tooLowGasPrice struct { + Code string `json:"code,omitempty"` + MinGasPrice string `json:"min_gas_price,omitempty"` + GotGasPrice string `json:"got_gas_price,omitempty"` +} + +func NewTooLowGasPrice(minGasPrice string, gotGasPrice string) *tooLowGasPrice { + return &tooLowGasPrice{Code: strconv.Itoa(int(TooLowGasPrice)), MinGasPrice: minGasPrice, GotGasPrice: gotGasPrice} +} + +type wrongChainID struct { + Code string `json:"code,omitempty"` + CurrentChainId string `json:"current_chain_id,omitempty"` + GotChainId string `json:"got_chain_id,omitempty"` +} + +func NewWrongChainID(currentChainId string, gotChainId string) *wrongChainID { + return &wrongChainID{Code: strconv.Itoa(int(WrongChainID)), CurrentChainId: currentChainId, GotChainId: gotChainId} +} + +type coinReserveUnderflow struct { + Code string `json:"code,omitempty"` + Delta string `json:"delta,omitempty"` + CoinReserve string `json:"coin_reserve,omitempty"` + CurrentReserve string `json:"current_reserve,omitempty"` + MinCoinReserve string `json:"min_coin_reserve,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewCoinReserveUnderflow(delta string, coinReserve string, currentReserve string, minCoinReserve string, coinSymbol string, coinId string) *coinReserveUnderflow { + return &coinReserveUnderflow{Code: strconv.Itoa(int(CoinReserveUnderflow)), Delta: delta, CoinReserve: coinReserve, CurrentReserve: currentReserve, MinCoinReserve: minCoinReserve, CoinSymbol: coinSymbol, CoinId: coinId} +} + +type coinAlreadyExists struct { + Code string `json:"code,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewCoinAlreadyExists(coinSymbol string, coinId string) *coinAlreadyExists { + return &coinAlreadyExists{Code: strconv.Itoa(int(CoinAlreadyExists)), CoinSymbol: coinSymbol, CoinId: coinId} +} + +type invalidCoinSymbol struct { + Code string `json:"code,omitempty"` + Pattern string `json:"pattern,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` +} + +func NewInvalidCoinSymbol(pattern string, coinSymbol string) *invalidCoinSymbol { + return &invalidCoinSymbol{Code: strconv.Itoa(int(InvalidCoinSymbol)), Pattern: pattern, CoinSymbol: coinSymbol} +} + +type invalidCoinName struct { + Code string `json:"code,omitempty"` + MaxBytes string `json:"max_bytes,omitempty"` + GotBytes string `json:"got_bytes,omitempty"` +} + +func NewInvalidCoinName(maxBytes string, gotBytes string) *invalidCoinName { + return &invalidCoinName{Code: strconv.Itoa(int(InvalidCoinName)), MaxBytes: maxBytes, GotBytes: gotBytes} +} + +type tooHighGasPrice struct { + Code string `json:"code,omitempty"` + MaxCheckGasPrice string `json:"max_check_gas_price,omitempty"` + CurrentGasPrice string `json:"current_gas_price,omitempty"` +} + +func NewTooHighGasPrice(maxCheckGasPrice, currentGasPrice string) *tooHighGasPrice { + return &tooHighGasPrice{Code: strconv.Itoa(int(TooHighGasPrice)), MaxCheckGasPrice: maxCheckGasPrice, CurrentGasPrice: currentGasPrice} +} + +type candidateExists struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` +} + +func NewCandidateExists(publicKey string) *candidateExists { + return &candidateExists{Code: strconv.Itoa(int(CandidateExists)), PublicKey: publicKey} +} + +type candidateNotFound struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` +} + +func NewCandidateNotFound(publicKey string) *candidateNotFound { + return &candidateNotFound{Code: strconv.Itoa(int(CandidateNotFound)), PublicKey: publicKey} +} + +type publicKeyInBlockList struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` +} + +func NewPublicKeyInBlockList(publicKey string) *publicKeyInBlockList { + return &publicKeyInBlockList{Code: strconv.Itoa(int(PublicKeyInBlockList)), PublicKey: publicKey} +} + +type newPublicKeyIsBad struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` + NewPublicKey string `json:"new_public_key,omitempty"` +} + +func NewNewPublicKeyIsBad(publicKey, newPublicKey string) *newPublicKeyIsBad { + return &newPublicKeyIsBad{Code: strconv.Itoa(int(NewPublicKeyIsBad)), PublicKey: publicKey, NewPublicKey: newPublicKey} +} + +type insufficientWaitList struct { + Code string `json:"code,omitempty"` + WaitlistValue string `json:"waitlist_value,omitempty"` + NeededValue string `json:"needed_value,omitempty"` +} + +func NewInsufficientWaitList(waitlistValue, neededValue string) *insufficientWaitList { + return &insufficientWaitList{Code: strconv.Itoa(int(InsufficientWaitList)), WaitlistValue: waitlistValue, NeededValue: neededValue} +} + +type stakeNotFound struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` + Owner string `json:"owner,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewStakeNotFound(publicKey string, owner string, coinId string, coinSymbol string) *stakeNotFound { + return &stakeNotFound{Code: strconv.Itoa(int(StakeNotFound)), PublicKey: publicKey, Owner: owner, CoinId: coinId, CoinSymbol: coinSymbol} +} + +type insufficientStake struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` + Owner string `json:"owner,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` + StakeValue string `json:"stake_value,omitempty"` + NeededValue string `json:"needed_value,omitempty"` +} + +func NewInsufficientStake(publicKey string, owner string, coinId string, coinSymbol string, stakeValue string, neededValue string) *insufficientStake { + return &insufficientStake{Code: strconv.Itoa(int(InsufficientStake)), PublicKey: publicKey, Owner: owner, CoinId: coinId, CoinSymbol: coinSymbol, StakeValue: stakeValue, NeededValue: neededValue} +} + +type tooLongNonce struct { + Code string `json:"code,omitempty"` + NonceBytes string `json:"nonce_bytes,omitempty"` + MaxNonceBytes string `json:"max_nonce_bytes,omitempty"` +} + +func NewTooLongNonce(nonceBytes string, maxNonceBytes string) *tooLongNonce { + return &tooLongNonce{Code: strconv.Itoa(int(TooLongNonce)), NonceBytes: nonceBytes, MaxNonceBytes: maxNonceBytes} +} + +type tooLargeOwnersList struct { + Code string `json:"code,omitempty"` + CountOwners string `json:"count_owners,omitempty"` + MaxCountOwners string `json:"max_count_owners,omitempty"` +} + +func NewTooLargeOwnersList(countOwners string, maxCountOwners string) *tooLargeOwnersList { + return &tooLargeOwnersList{Code: strconv.Itoa(int(TooLargeOwnersList)), CountOwners: countOwners, MaxCountOwners: maxCountOwners} +} + +type incorrectWeights struct { + Code string `json:"code,omitempty"` + Address string `json:"address,omitempty"` + Weight string `json:"weight,omitempty"` + MaxWeight string `json:"max_weight,omitempty"` +} + +func NewIncorrectWeights(address string, weight string, maxWeight string) *incorrectWeights { + return &incorrectWeights{Code: strconv.Itoa(int(IncorrectWeights)), Address: address, Weight: weight, MaxWeight: maxWeight} +} + +type incorrectTotalWeights struct { + Code string `json:"code,omitempty"` + TotalWeights string `json:"total_weights,omitempty"` + Threshold string `json:"threshold,omitempty"` +} + +func NewIncorrectTotalWeights(totalWeight, threshold string) *incorrectTotalWeights { + return &incorrectTotalWeights{Code: strconv.Itoa(int(IncorrectTotalWeights)), Threshold: threshold, TotalWeights: totalWeight} +} + +type differentCountAddressesAndWeights struct { + Code string `json:"code,omitempty"` + CountAddresses string `json:"count_addresses,omitempty"` + CountWeights string `json:"count_weights,omitempty"` +} + +func NewDifferentCountAddressesAndWeights(countAddresses string, countWeights string) *differentCountAddressesAndWeights { + return &differentCountAddressesAndWeights{Code: strconv.Itoa(int(DifferentCountAddressesAndWeights)), CountAddresses: countAddresses, CountWeights: countWeights} +} + +type minimumValueToBuyReached struct { + Code string `json:"code,omitempty"` + MinimumValueToBuy string `json:"minimum_value_to_buy,omitempty"` + WillGetValue string `json:"will_get_value,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewMinimumValueToBuyReached(minimumValueToBuy string, willGetValue string, coinSymbol string, coinId string) *minimumValueToBuyReached { + return &minimumValueToBuyReached{Code: strconv.Itoa(int(MinimumValueToBuyReached)), MinimumValueToBuy: minimumValueToBuy, WillGetValue: willGetValue, CoinSymbol: coinSymbol, CoinId: coinId} +} + +type maximumValueToSellReached struct { + Code string `json:"code,omitempty"` + MaximumValueToSell string `json:"maximum_value_to_sell,omitempty"` + NeededSpendValue string `json:"needed_spend_value,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewMaximumValueToSellReached(maximumValueToSell string, neededSpendValue string, coinSymbol string, coinId string) *maximumValueToSellReached { + return &maximumValueToSellReached{Code: strconv.Itoa(int(MaximumValueToSellReached)), MaximumValueToSell: maximumValueToSell, NeededSpendValue: neededSpendValue, CoinSymbol: coinSymbol, CoinId: coinId} +} + +type duplicatedAddresses struct { + Code string `json:"code,omitempty"` + Address string `json:"address,omitempty"` +} + +func NewDuplicatedAddresses(address string) *duplicatedAddresses { + return &duplicatedAddresses{Code: strconv.Itoa(int(DuplicatedAddresses)), Address: address} +} + +type checkInvalidLock struct { + Code string `json:"code,omitempty"` +} + +func NewCheckInvalidLock() *checkInvalidLock { + return &checkInvalidLock{Code: strconv.Itoa(int(CheckInvalidLock))} +} + +type crossConvert struct { + Code string `json:"code,omitempty"` + CoinIdToSell string `json:"coin_id_to_sell,omitempty"` + CoinToSell string `json:"coin_to_sell,omitempty"` + CoinIdToBuy string `json:"coin_id_to_buy,omitempty"` + CoinToBuy string `json:"coin_to_buy,omitempty"` +} + +func NewCrossConvert(coinIdToSell string, coinToSell string, coinIdToBuy string, coinToBuy string) *crossConvert { + return &crossConvert{Code: strconv.Itoa(int(CrossConvert)), CoinIdToSell: coinIdToSell, CoinToSell: coinToSell, CoinIdToBuy: coinIdToBuy, CoinToBuy: coinToBuy} +} + +type isNotOwnerOfCoin struct { + Code string `json:"code,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + Owner *string `json:"owner"` +} + +func NewIsNotOwnerOfCoin(coinSymbol string, owner *string) *isNotOwnerOfCoin { + var own *string + if owner != nil { + own = owner + } + return &isNotOwnerOfCoin{Code: strconv.Itoa(int(IsNotOwnerOfCoin)), CoinSymbol: coinSymbol, Owner: own} +} + +type isNotOwnerOfCandidate struct { + Code string `json:"code,omitempty"` + Sender string `json:"sender,omitempty"` + PublicKey string `json:"public_key,omitempty"` + Owner string `json:"owner,omitempty"` + Control string `json:"control,omitempty"` +} + +func NewIsNotOwnerOfCandidate(sender, pubKey string, owner, control string) *isNotOwnerOfCandidate { + return &isNotOwnerOfCandidate{Code: strconv.Itoa(int(IsNotOwnerOfCandidate)), PublicKey: pubKey, Owner: owner, Control: control, Sender: sender} +} + +type checkExpired struct { + Code string `json:"code,omitempty"` + DueBlock string `json:"due_block,omitempty"` + CurrentBlock string `json:"current_block,omitempty"` +} + +func MewCheckExpired(dueBlock string, currentBlock string) *checkExpired { + return &checkExpired{Code: strconv.Itoa(int(CheckExpired)), DueBlock: dueBlock, CurrentBlock: currentBlock} +} + +type checkUsed struct { + Code string `json:"code,omitempty"` +} + +func NewCheckUsed() *checkUsed { + return &checkUsed{Code: strconv.Itoa(int(CheckUsed))} +} + +type notEnoughMultisigVotes struct { + Code string `json:"code,omitempty"` + NeededVotes string `json:"needed_votes,omitempty"` + GotVotes string `json:"got_votes,omitempty"` +} + +func NewNotEnoughMultisigVotes(neededVotes, gotVotes string) *notEnoughMultisigVotes { + return ¬EnoughMultisigVotes{Code: strconv.Itoa(int(NotEnoughMultisigVotes)), NeededVotes: neededVotes, GotVotes: gotVotes} +} + +type incorrectMultiSignature struct { + Code string `json:"code,omitempty"` +} + +func NewIncorrectMultiSignature() *incorrectMultiSignature { + return &incorrectMultiSignature{Code: strconv.Itoa(int(IncorrectMultiSignature))} +} + +type wrongCrr struct { + Code string `json:"code,omitempty"` + MaxCrr string `json:"max_crr,omitempty"` + MinCrr string `json:"min_crr,omitempty"` + GotCrr string `json:"got_crr,omitempty"` +} + +func NewWrongCrr(min string, max string, got string) *wrongCrr { + return &wrongCrr{Code: strconv.Itoa(int(WrongCrr)), MinCrr: min, MaxCrr: max, GotCrr: got} +} + +type stakeShouldBePositive struct { + Code string `json:"code,omitempty"` + Stake string `json:"stake,omitempty"` +} + +func NewStakeShouldBePositive(stake string) *stakeShouldBePositive { + return &stakeShouldBePositive{Code: strconv.Itoa(int(StakeShouldBePositive)), Stake: stake} +} + +type wrongHaltHeight struct { + Code string `json:"code,omitempty"` + PublicKey string `json:"public_key,omitempty"` + Height string `json:"height,omitempty"` +} + +func NewWrongHaltHeight(height string, pubkey string) *wrongHaltHeight { + return &wrongHaltHeight{Code: strconv.Itoa(int(WrongHaltHeight)), Height: height, PublicKey: pubkey} +} + +type tooLowStake struct { + Code string `json:"code,omitempty"` + Sender string `json:"sender,omitempty"` + PublicKey string `json:"public_key,omitempty"` + Value string `json:"value,omitempty"` + CoinSymbol string `json:"coin_symbol,omitempty"` + CoinId string `json:"coin_id,omitempty"` +} + +func NewTooLowStake(sender string, pubKey string, value string, coinId string, coinSymbol string) *tooLowStake { + return &tooLowStake{Code: strconv.Itoa(int(TooLowStake)), Sender: sender, PublicKey: pubKey, Value: value, CoinId: coinId, CoinSymbol: coinSymbol} +} + +type wrongCommission struct { + Code string `json:"code,omitempty"` + GotCommission string `json:"got_commission,omitempty"` + MinCommission string `json:"min_commission,omitempty"` + MaxCommission string `json:"max_commission,omitempty"` +} + +func NewWrongCommission(got string, min string, max string) *wrongCommission { + return &wrongCommission{Code: strconv.Itoa(int(WrongCommission)), MaxCommission: max, MinCommission: min, GotCommission: got} +} + +type multisigNotExists struct { + Code string `json:"code,omitempty"` + Address string `json:"address,omitempty"` +} + +func NewMultisigNotExists(address string) *multisigNotExists { + return &multisigNotExists{Code: strconv.Itoa(int(MultisigNotExists)), Address: address} +} + +type multisigExists struct { + Code string `json:"code,omitempty"` + Address string `json:"address,omitempty"` +} + +func NewMultisigExists(address string) *multisigExists { + return &multisigExists{Code: strconv.Itoa(int(MultisigExists)), Address: address} +} + +type wrongCoinSupply struct { + Code string `json:"code,omitempty"` + + MaxCoinSupply string `json:"max_coin_supply,omitempty"` + CurrentCoinSupply string `json:"current_coin_supply,omitempty"` + + MinInitialReserve string `json:"min_initial_reserve,omitempty"` + CurrentInitialReserve string `json:"current_initial_reserve,omitempty"` + + MinInitialAmount string `json:"min_initial_amount,omitempty"` + MaxInitialAmount string `json:"max_initial_amount,omitempty"` + CurrentInitialAmount string `json:"current_initial_amount,omitempty"` +} + +func NewWrongCoinSupply(maxCoinSupply string, currentCoinSupply string, minInitialReserve string, currentInitialReserve string, minInitialAmount string, maxInitialAmount string, currentInitialAmount string) *wrongCoinSupply { + return &wrongCoinSupply{Code: strconv.Itoa(int(WrongCoinSupply)), MaxCoinSupply: maxCoinSupply, CurrentCoinSupply: currentCoinSupply, MinInitialReserve: minInitialReserve, CurrentInitialReserve: currentInitialReserve, MinInitialAmount: minInitialAmount, MaxInitialAmount: maxInitialAmount, CurrentInitialAmount: currentInitialAmount} +} diff --git a/core/commissions/commissions.go b/core/commissions/commissions.go index e90b8e399..aa7b21f51 100644 --- a/core/commissions/commissions.go +++ b/core/commissions/commissions.go @@ -3,15 +3,21 @@ package commissions // all commissions are divided by 10^15 // actual commission is SendTx * 10^15 = 10 000 000 000 000 000 PIP = 0,01 BIP const ( - SendTx int64 = 10 - CreateMultisig int64 = 100 - ConvertTx int64 = 100 - DeclareCandidacyTx int64 = 10000 - DelegateTx int64 = 200 - UnbondTx int64 = 200 - PayloadByte int64 = 2 - ToggleCandidateStatus int64 = 100 - EditCandidate int64 = 10000 - MultisendDelta int64 = 5 - RedeemCheckTx int64 = SendTx * 3 + SendTx int64 = 10 + CreateMultisig int64 = 100 + ConvertTx int64 = 100 + DeclareCandidacyTx int64 = 10000 + DelegateTx int64 = 200 + UnbondTx int64 = 200 + PayloadByte int64 = 2 + ToggleCandidateStatus int64 = 100 + EditCandidate int64 = 10000 + EditCandidatePublicKey int64 = 100000000 + MultisendDelta int64 = 5 + RedeemCheckTx = SendTx * 3 + SetHaltBlock int64 = 1000 + RecreateCoin int64 = 10000000 + EditOwner int64 = 10000000 + EditMultisigData int64 = 1000 + PriceVoteData int64 = 10 ) diff --git a/core/dao/dao.go b/core/dao/dao.go index 6d722a3ba..8134a503c 100644 --- a/core/dao/dao.go +++ b/core/dao/dao.go @@ -1,15 +1,11 @@ package dao import ( - "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/types" ) +// Commission which is subtracted from rewards and being send to DAO Address var ( - Address = (&accounts.Multisig{ - Threshold: 2, - Weights: []uint{1, 1, 1}, - Addresses: []types.Address{types.HexToAddress("Mxed2f3dbe7a25f928df95ae8f207ed8079578daf3"), types.HexToAddress("Mx91980bf6391eb6946f43df559fd1e56952f9cde7"), types.HexToAddress("Mx375bc810e0fd19dcf0da43556b50ba6825ba11b8")}, - }).Address() - Commission = 10 + Address = types.HexToAddress("Mx7f0fc21d932f38ca9444f61703174569066cfa50") + Commission = 10 // in % ) diff --git a/core/developers/developers.go b/core/developers/developers.go index 434d6047c..104d53c70 100644 --- a/core/developers/developers.go +++ b/core/developers/developers.go @@ -1,15 +1,11 @@ package developers import ( - "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/types" ) +// Commission which is subtracted from rewards and being send to Developers Address var ( - Address = (&accounts.Multisig{ - Threshold: 2, - Weights: []uint{1, 1, 1}, - Addresses: []types.Address{types.HexToAddress("Mx22df0f98c1b421974fb0c64440258aaffd4e96d8"), types.HexToAddress("Mxf8821646818a873e3efac40cc2c13f96e3515aa1"), types.HexToAddress("Mx90a82ed6fd69cdd125474ce9349a8e34fb4f5ffe")}, - }).Address() - Commission = 10 + Address = types.HexToAddress("Mx688568d9d70c57e71d0b9de6480afb0d317f885c") + Commission = 10 // in % ) diff --git a/core/events/store.go b/core/events/store.go new file mode 100644 index 000000000..f8bfed745 --- /dev/null +++ b/core/events/store.go @@ -0,0 +1,216 @@ +package events + +import ( + "encoding/binary" + "github.com/tendermint/go-amino" + db "github.com/tendermint/tm-db" + "sync" +) + +// IEventsDB is an interface of Events +type IEventsDB interface { + AddEvent(height uint32, event Event) + LoadEvents(height uint32) Events + CommitEvents() error +} + +type eventsStore struct { + cdc *amino.Codec + sync.RWMutex + db db.DB + pending pendingEvents + idPubKey map[uint16][32]byte + pubKeyID map[[32]byte]uint16 + idAddress map[uint32][20]byte + addressID map[[20]byte]uint32 +} + +type pendingEvents struct { + sync.Mutex + height uint32 + items Events +} + +// NewEventsStore creates new events store in given DB +func NewEventsStore(db db.DB) IEventsDB { + codec := amino.NewCodec() + codec.RegisterInterface((*Event)(nil), nil) + codec.RegisterInterface((*compactEvent)(nil), nil) + codec.RegisterConcrete(&reward{}, "reward", nil) + codec.RegisterConcrete(&slash{}, "slash", nil) + codec.RegisterConcrete(&unbond{}, "unbond", nil) + codec.RegisterConcrete(&stakeKick{}, "stakeKick", nil) + + return &eventsStore{ + cdc: codec, + RWMutex: sync.RWMutex{}, + db: db, + pending: pendingEvents{}, + idPubKey: make(map[uint16][32]byte), + pubKeyID: make(map[[32]byte]uint16), + idAddress: make(map[uint32][20]byte), + addressID: make(map[[20]byte]uint32), + } +} + +func (store *eventsStore) cachePubKey(id uint16, key [32]byte) { + store.idPubKey[id] = key + store.pubKeyID[key] = id +} + +func (store *eventsStore) cacheAddress(id uint32, address [20]byte) { + store.idAddress[id] = address + store.addressID[address] = id +} + +func (store *eventsStore) AddEvent(height uint32, event Event) { + store.pending.Lock() + defer store.pending.Unlock() + if store.pending.height != height { + store.pending.items = Events{} + } + store.pending.items = append(store.pending.items, event) + store.pending.height = height +} + +func (store *eventsStore) LoadEvents(height uint32) Events { + store.loadCache() + + bytes, err := store.db.Get(uint32ToBytes(height)) + if err != nil { + panic(err) + } + if len(bytes) == 0 { + return Events{} + } + + var items []compactEvent + if err := store.cdc.UnmarshalBinaryBare(bytes, &items); err != nil { + panic(err) + } + + resultEvents := make(Events, 0, len(items)) + for _, compactEvent := range items { + event := compactEvent.compile(store.idPubKey[compactEvent.pubKeyID()], store.idAddress[compactEvent.addressID()]) + resultEvents = append(resultEvents, event) + } + + return resultEvents +} + +func (store *eventsStore) CommitEvents() error { + store.loadCache() + + store.pending.Lock() + defer store.pending.Unlock() + var data []compactEvent + for _, item := range store.pending.items { + pubKey := store.savePubKey(item.validatorPubKey()) + address := store.saveAddress(item.address()) + data = append(data, item.convert(pubKey, address)) + } + + bytes, err := store.cdc.MarshalBinaryBare(data) + if err != nil { + return err + } + + store.Lock() + defer store.Unlock() + if err := store.db.Set(uint32ToBytes(store.pending.height), bytes); err != nil { + return err + } + return nil +} + +func (store *eventsStore) loadCache() { + store.Lock() + if len(store.idPubKey) == 0 { + store.loadPubKeys() + store.loadAddresses() + } + store.Unlock() +} + +const pubKeyPrefix = "pubKey" +const addressPrefix = "address" +const pubKeysCountKey = "pubKeys" +const addressesCountKey = "addresses" + +func (store *eventsStore) saveAddress(address [20]byte) uint32 { + + if id, ok := store.addressID[address]; ok { + return id + } + + id := uint32(len(store.addressID)) + store.cacheAddress(id, address) + + if err := store.db.Set(append([]byte(addressPrefix), uint32ToBytes(id)...), address[:]); err != nil { + panic(err) + } + if err := store.db.Set([]byte(addressesCountKey), uint32ToBytes(uint32(len(store.addressID)))); err != nil { + panic(err) + } + return id +} + +func (store *eventsStore) savePubKey(validatorPubKey [32]byte) uint16 { + + key := validatorPubKey + if id, ok := store.pubKeyID[key]; ok { + return id + } + + id := uint16(len(store.idPubKey)) + store.cachePubKey(id, key) + + if err := store.db.Set(append([]byte(pubKeyPrefix), uint16ToBytes(id)...), validatorPubKey[:]); err != nil { + panic(err) + } + if err := store.db.Set([]byte(pubKeysCountKey), uint16ToBytes(uint16(len(store.idPubKey)))); err != nil { + panic(err) + } + return id +} + +func (store *eventsStore) loadPubKeys() { + if count, _ := store.db.Get([]byte(pubKeysCountKey)); len(count) > 0 { + for id := uint16(0); id < binary.BigEndian.Uint16(count); id++ { + key, err := store.db.Get(append([]byte(pubKeyPrefix), uint16ToBytes(id)...)) + if err != nil { + panic(err) + } + var pubKey [32]byte + copy(pubKey[:], key) + store.cachePubKey(id, pubKey) + } + } +} + +func (store *eventsStore) loadAddresses() { + count, err := store.db.Get([]byte(addressesCountKey)) + if err != nil { + panic(err) + } + if len(count) > 0 { + for id := uint32(0); id < binary.BigEndian.Uint32(count); id++ { + address, _ := store.db.Get(append([]byte(addressPrefix), uint32ToBytes(id)...)) + var key [20]byte + copy(key[:], address) + store.cacheAddress(id, key) + } + } +} + +func uint32ToBytes(height uint32) []byte { + var h = make([]byte, 4) + binary.BigEndian.PutUint32(h, height) + return h +} + +func uint16ToBytes(height uint16) []byte { + var h = make([]byte, 2) + binary.BigEndian.PutUint16(h, height) + return h +} diff --git a/core/events/store_test.go b/core/events/store_test.go new file mode 100644 index 000000000..3b11f8045 --- /dev/null +++ b/core/events/store_test.go @@ -0,0 +1,169 @@ +package events + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + db "github.com/tendermint/tm-db" + "testing" +) + +func TestIEventsDB(t *testing.T) { + store := NewEventsStore(db.NewMemDB()) + + { + event := &RewardEvent{ + Role: RoleDevelopers.String(), + Address: types.HexToAddress("Mx04bea23efb744dc93b4fda4c20bf4a21c6e195f1"), + Amount: "111497225000000000000", + ValidatorPubKey: types.HexToPubkey("Mp9e13f2f5468dd782b316444fbd66595e13dba7d7bd3efa1becd50b42045f58c6"), + } + store.AddEvent(12, event) + } + { + event := &StakeKickEvent{ + Coin: 1, + Address: types.HexToAddress("Mx18467bbb64a8edf890201d526c35957d82be3d95"), + Amount: "891977800000000000000", + ValidatorPubKey: types.HexToPubkey("Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd1c"), + } + store.AddEvent(12, event) + } + err := store.CommitEvents() + if err != nil { + t.Fatal(err) + } + + { + event := &UnbondEvent{ + Coin: 1, + Address: types.HexToAddress("Mx18467bbb64a8edf890201d526c35957d82be3d91"), + Amount: "891977800000000000001", + ValidatorPubKey: types.HexToPubkey("Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd11"), + } + store.AddEvent(14, event) + } + { + event := &UnbondEvent{ + Coin: 2, + Address: types.HexToAddress("Mx18467bbb64a8edf890201d526c35957d82be3d92"), + Amount: "891977800000000000002", + ValidatorPubKey: types.HexToPubkey("Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd12"), + } + store.AddEvent(14, event) + } + err = store.CommitEvents() + if err != nil { + t.Fatal(err) + } + + { + event := &SlashEvent{ + Coin: 10, + Address: types.HexToAddress("Mx18467bbb64a8edf890201d526c35957d82be3d10"), + Amount: "891977800000000000010", + ValidatorPubKey: types.HexToPubkey("Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd10"), + } + store.AddEvent(11, event) + } + err = store.CommitEvents() + if err != nil { + t.Fatal(err) + } + + loadEvents := store.LoadEvents(12) + + if len(loadEvents) != 2 { + t.Fatalf("count of events not equal 2, got %d", len(loadEvents)) + } + + if loadEvents[0].Type() != TypeRewardEvent { + t.Fatal("invalid event type") + } + if loadEvents[0].(*RewardEvent).Amount != "111497225000000000000" { + t.Fatal("invalid Amount") + } + if loadEvents[0].(*RewardEvent).Address.String() != "Mx04bea23efb744dc93b4fda4c20bf4a21c6e195f1" { + t.Fatal("invalid Address") + } + if loadEvents[0].(*RewardEvent).ValidatorPubKey.String() != "Mp9e13f2f5468dd782b316444fbd66595e13dba7d7bd3efa1becd50b42045f58c6" { + t.Fatal("invalid PubKey") + } + if loadEvents[0].(*RewardEvent).Role != RoleDevelopers.String() { + t.Fatal("invalid Role") + } + + if loadEvents[1].Type() != TypeStakeKickEvent { + t.Fatal("invalid event type") + } + if loadEvents[1].(*StakeKickEvent).Amount != "891977800000000000000" { + t.Fatal("invalid Amount") + } + if loadEvents[1].(*StakeKickEvent).Address.String() != "Mx18467bbb64a8edf890201d526c35957d82be3d95" { + t.Fatal("invalid Address") + } + if loadEvents[1].(*StakeKickEvent).ValidatorPubKey.String() != "Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd1c" { + t.Fatal("invalid PubKey") + } + if loadEvents[1].(*StakeKickEvent).Coin != 1 { + t.Fatal("invalid Coin") + } + + loadEvents = store.LoadEvents(14) + + if len(loadEvents) != 2 { + t.Fatal("count of events not equal 2") + } + + if loadEvents[0].Type() != TypeUnbondEvent { + t.Fatal("invalid event type") + } + if loadEvents[0].(*UnbondEvent).Amount != "891977800000000000001" { + t.Fatal("invalid Amount") + } + if loadEvents[0].(*UnbondEvent).Address.String() != "Mx18467bbb64a8edf890201d526c35957d82be3d91" { + t.Fatal("invalid Address") + } + if loadEvents[0].(*UnbondEvent).ValidatorPubKey.String() != "Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd11" { + t.Fatal("invalid PubKey") + } + if loadEvents[0].(*UnbondEvent).Coin != 1 { + t.Fatal("invalid Coin") + } + + if loadEvents[1].Type() != TypeUnbondEvent { + t.Fatal("invalid event type") + } + if loadEvents[1].(*UnbondEvent).Amount != "891977800000000000002" { + t.Fatal("invalid Amount") + } + if loadEvents[1].(*UnbondEvent).Address.String() != "Mx18467bbb64a8edf890201d526c35957d82be3d92" { + t.Fatal("invalid Address") + } + if loadEvents[1].(*UnbondEvent).ValidatorPubKey.String() != "Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd12" { + t.Fatal("invalid PubKey") + } + if loadEvents[1].(*UnbondEvent).Coin != 2 { + t.Fatal("invalid Coin") + } + + loadEvents = store.LoadEvents(11) + + if len(loadEvents) != 1 { + t.Fatal("count of events not equal 1") + } + + if loadEvents[0].Type() != TypeSlashEvent { + t.Fatal("invalid event type") + } + if loadEvents[0].(*SlashEvent).Amount != "891977800000000000010" { + t.Fatal("invalid Amount") + } + if loadEvents[0].(*SlashEvent).Address.String() != "Mx18467bbb64a8edf890201d526c35957d82be3d10" { + t.Fatal("invalid Address") + } + if loadEvents[0].(*SlashEvent).ValidatorPubKey.String() != "Mp738da41ba6a7b7d69b7294afa158b89c5a1b410cbf0c2443c85c5fe24ad1dd10" { + t.Fatal("invalid PubKey") + } + if loadEvents[0].(*SlashEvent).Coin != 10 { + t.Fatal("invalid Coin") + } +} diff --git a/core/events/types.go b/core/events/types.go new file mode 100644 index 000000000..34b674477 --- /dev/null +++ b/core/events/types.go @@ -0,0 +1,328 @@ +package events + +import ( + "fmt" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/tendermint/go-amino" + "math/big" +) + +// Event type names +const ( + TypeRewardEvent = "minter/RewardEvent" + TypeSlashEvent = "minter/SlashEvent" + TypeUnbondEvent = "minter/UnbondEvent" + TypeStakeKickEvent = "minter/StakeKickEvent" +) + +func RegisterAminoEvents(codec *amino.Codec) { + codec.RegisterInterface((*Event)(nil), nil) + codec.RegisterConcrete(RewardEvent{}, + TypeRewardEvent, nil) + codec.RegisterConcrete(SlashEvent{}, + TypeSlashEvent, nil) + codec.RegisterConcrete(UnbondEvent{}, + TypeUnbondEvent, nil) + codec.RegisterConcrete(StakeKickEvent{}, + TypeStakeKickEvent, nil) +} + +type Event interface { + Type() string + AddressString() string + ValidatorPubKeyString() string + validatorPubKey() types.Pubkey + address() types.Address + convert(pubKeyID uint16, addressID uint32) compactEvent +} + +type compactEvent interface { + compile(pubKey [32]byte, address [20]byte) Event + addressID() uint32 + pubKeyID() uint16 +} + +type Events []Event + +type Role byte + +const ( + RoleValidator Role = iota + RoleDelegator + RoleDAO + RoleDevelopers +) + +func (r Role) String() string { + switch r { + case RoleValidator: + return "Validator" + case RoleDelegator: + return "Delegator" + case RoleDAO: + return "DAO" + case RoleDevelopers: + return "Developers" + } + + panic(fmt.Sprintf("undefined role: %d", r)) +} + +func NewRole(r string) Role { + switch r { + case "Validator": + return RoleValidator + case "Delegator": + return RoleDelegator + case "DAO": + return RoleDAO + case "Developers": + return RoleDevelopers + } + + panic("undefined role: " + r) +} + +type reward struct { + Role Role + AddressID uint32 + Amount []byte + PubKeyID uint16 +} + +func (r *reward) compile(pubKey [32]byte, address [20]byte) Event { + event := new(RewardEvent) + event.ValidatorPubKey = pubKey + event.Address = address + event.Role = r.Role.String() + event.Amount = big.NewInt(0).SetBytes(r.Amount).String() + return event +} + +func (r *reward) addressID() uint32 { + return r.AddressID +} + +func (r *reward) pubKeyID() uint16 { + return r.PubKeyID +} + +type RewardEvent struct { + Role string `json:"role"` + Address types.Address `json:"address"` + Amount string `json:"amount"` + ValidatorPubKey types.Pubkey `json:"validator_pub_key"` +} + +func (re *RewardEvent) Type() string { + return TypeRewardEvent +} + +func (re *RewardEvent) AddressString() string { + return re.Address.String() +} + +func (re *RewardEvent) address() types.Address { + return re.Address +} + +func (re *RewardEvent) ValidatorPubKeyString() string { + return re.ValidatorPubKey.String() +} + +func (re *RewardEvent) validatorPubKey() types.Pubkey { + return re.ValidatorPubKey +} + +func (re *RewardEvent) convert(pubKeyID uint16, addressID uint32) compactEvent { + result := new(reward) + result.AddressID = addressID + result.Role = NewRole(re.Role) + bi, _ := big.NewInt(0).SetString(re.Amount, 10) + result.Amount = bi.Bytes() + result.PubKeyID = pubKeyID + return result +} + +type slash struct { + AddressID uint32 + Amount []byte + Coin uint32 + PubKeyID uint16 +} + +func (s *slash) compile(pubKey [32]byte, address [20]byte) Event { + event := new(SlashEvent) + event.ValidatorPubKey = pubKey + event.Address = address + event.Coin = uint64(s.Coin) + event.Amount = big.NewInt(0).SetBytes(s.Amount).String() + return event +} + +func (s *slash) addressID() uint32 { + return s.AddressID +} + +func (s *slash) pubKeyID() uint16 { + return s.PubKeyID +} + +type SlashEvent struct { + Address types.Address `json:"address"` + Amount string `json:"amount"` + Coin uint64 `json:"coin"` + ValidatorPubKey types.Pubkey `json:"validator_pub_key"` +} + +func (se *SlashEvent) Type() string { + return TypeSlashEvent +} + +func (se *SlashEvent) AddressString() string { + return se.Address.String() +} + +func (se *SlashEvent) address() types.Address { + return se.Address +} + +func (se *SlashEvent) ValidatorPubKeyString() string { + return se.ValidatorPubKey.String() +} + +func (se *SlashEvent) validatorPubKey() types.Pubkey { + return se.ValidatorPubKey +} + +func (se *SlashEvent) convert(pubKeyID uint16, addressID uint32) compactEvent { + result := new(slash) + result.AddressID = addressID + result.Coin = uint32(se.Coin) + bi, _ := big.NewInt(0).SetString(se.Amount, 10) + result.Amount = bi.Bytes() + result.PubKeyID = pubKeyID + return result +} + +type unbond struct { + AddressID uint32 + Amount []byte + Coin uint32 + PubKeyID uint16 +} + +func (u *unbond) compile(pubKey [32]byte, address [20]byte) Event { + event := new(UnbondEvent) + event.ValidatorPubKey = pubKey + event.Address = address + event.Coin = uint64(u.Coin) + event.Amount = big.NewInt(0).SetBytes(u.Amount).String() + return event +} + +func (u *unbond) addressID() uint32 { + return u.AddressID +} + +func (u *unbond) pubKeyID() uint16 { + return u.PubKeyID +} + +type UnbondEvent struct { + Address types.Address `json:"address"` + Amount string `json:"amount"` + Coin uint64 `json:"coin"` + ValidatorPubKey types.Pubkey `json:"validator_pub_key"` +} + +func (ue *UnbondEvent) Type() string { + return TypeUnbondEvent +} + +func (ue *UnbondEvent) AddressString() string { + return ue.Address.String() +} + +func (ue *UnbondEvent) address() types.Address { + return ue.Address +} + +func (ue *UnbondEvent) ValidatorPubKeyString() string { + return ue.ValidatorPubKey.String() +} + +func (ue *UnbondEvent) validatorPubKey() types.Pubkey { + return ue.ValidatorPubKey +} + +func (ue *UnbondEvent) convert(pubKeyID uint16, addressID uint32) compactEvent { + result := new(unbond) + result.AddressID = addressID + result.Coin = uint32(ue.Coin) + bi, _ := big.NewInt(0).SetString(ue.Amount, 10) + result.Amount = bi.Bytes() + result.PubKeyID = pubKeyID + return result +} + +type stakeKick struct { + AddressID uint32 + Amount []byte + Coin uint32 + PubKeyID uint16 +} + +func (u *stakeKick) compile(pubKey [32]byte, address [20]byte) Event { + event := new(StakeKickEvent) + event.ValidatorPubKey = pubKey + event.Address = address + event.Coin = uint64(u.Coin) + event.Amount = big.NewInt(0).SetBytes(u.Amount).String() + return event +} + +func (u *stakeKick) addressID() uint32 { + return u.AddressID +} + +func (u *stakeKick) pubKeyID() uint16 { + return u.PubKeyID +} + +type StakeKickEvent struct { + Address types.Address `json:"address"` + Amount string `json:"amount"` + Coin uint64 `json:"coin"` + ValidatorPubKey types.Pubkey `json:"validator_pub_key"` +} + +func (ue *StakeKickEvent) Type() string { + return TypeStakeKickEvent +} + +func (ue *StakeKickEvent) AddressString() string { + return ue.Address.String() +} + +func (ue *StakeKickEvent) address() types.Address { + return ue.Address +} + +func (ue *StakeKickEvent) ValidatorPubKeyString() string { + return ue.ValidatorPubKey.String() +} + +func (ue *StakeKickEvent) validatorPubKey() types.Pubkey { + return ue.ValidatorPubKey +} + +func (ue *StakeKickEvent) convert(pubKeyID uint16, addressID uint32) compactEvent { + result := new(stakeKick) + result.AddressID = addressID + result.Coin = uint32(ue.Coin) + bi, _ := big.NewInt(0).SetString(ue.Amount, 10) + result.Amount = bi.Bytes() + result.PubKeyID = pubKeyID + return result +} diff --git a/core/minter/minter.go b/core/minter/minter.go index a76eb4700..6d457e4e3 100644 --- a/core/minter/minter.go +++ b/core/minter/minter.go @@ -3,10 +3,10 @@ package minter import ( "bytes" "fmt" - eventsdb "github.com/MinterTeam/events-db" "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/appdb" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/rewards" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/state/candidates" @@ -14,46 +14,40 @@ import ( "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/core/validators" - "github.com/MinterTeam/minter-go-node/helpers" - "github.com/MinterTeam/minter-go-node/upgrades" "github.com/MinterTeam/minter-go-node/version" "github.com/syndtr/goleveldb/leveldb/filter" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/tendermint/go-amino" abciTypes "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" - cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" - "github.com/tendermint/tendermint/evidence" tmNode "github.com/tendermint/tendermint/node" - rpctypes "github.com/tendermint/tendermint/rpc/lib/types" - types2 "github.com/tendermint/tendermint/types" "github.com/tendermint/tm-db" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "math/big" "sort" - "strings" "sync" "sync/atomic" "time" ) +// Statuses of validators const ( - // Global validator's statuses ValidatorPresent = 1 ValidatorAbsent = 2 +) - BlockMaxBytes = 10000000 - - DefaultMaxGas = 100000 - MinMaxGas = 5000 +// Block params +const ( + blockMaxBytes = 10000000 + defaultMaxGas = 100000 + minMaxGas = 5000 ) +const votingPowerConsensus = 2. / 3. + var ( blockchain *Blockchain ) -// Main structure of Minter Blockchain +// Blockchain is a main structure of Minter type Blockchain struct { abciTypes.BaseApplication @@ -63,7 +57,7 @@ type Blockchain struct { appDB *appdb.AppDB eventsDB eventsdb.IEventsDB stateDeliver *state.State - stateCheck *state.State + stateCheck *state.CheckState height uint64 // current Blockchain height rewards *big.Int // Rewards pool validatorsStatuses map[types.TmAddress]int8 @@ -80,7 +74,7 @@ type Blockchain struct { cfg *config.Config } -// Creates Minter Blockchain instance, should be only called once +// NewMinterBlockchain creates Minter Blockchain instance, should be only called once func NewMinterBlockchain(cfg *config.Config) *Blockchain { var err error @@ -107,7 +101,7 @@ func NewMinterBlockchain(cfg *config.Config) *Blockchain { } // Set stateDeliver and stateCheck - blockchain.stateDeliver, err = state.NewState(blockchain.height, blockchain.stateDB, blockchain.eventsDB, cfg.KeepLastStates, cfg.StateCacheSize) + blockchain.stateDeliver, err = state.NewState(blockchain.height, blockchain.stateDB, blockchain.eventsDB, cfg.StateCacheSize, cfg.KeepLastStates) if err != nil { panic(err) } @@ -116,14 +110,13 @@ func NewMinterBlockchain(cfg *config.Config) *Blockchain { // Set start height for rewards and validators rewards.SetStartHeight(applicationDB.GetStartHeight()) - validators.SetStartHeight(applicationDB.GetStartHeight()) blockchain.haltHeight = uint64(cfg.HaltHeight) return blockchain } -// Initialize blockchain with validators and other info. Only called once. +// InitChain initialize blockchain with validators and other info. Only called once. func (app *Blockchain) InitChain(req abciTypes.RequestInitChain) abciTypes.ResponseInitChain { var genesisState types.AppState if err := amino.UnmarshalJSON(req.AppStateBytes, &genesisState); err != nil { @@ -134,55 +127,22 @@ func (app *Blockchain) InitChain(req abciTypes.RequestInitChain) abciTypes.Respo panic(err) } - totalPower := big.NewInt(0) - for _, val := range genesisState.Validators { - totalPower.Add(totalPower, helpers.StringToBigInt(val.TotalBipStake)) - } - - vals := make([]abciTypes.ValidatorUpdate, len(genesisState.Validators)) - for i, val := range genesisState.Validators { - var validatorPubKey ed25519.PubKeyEd25519 - copy(validatorPubKey[:], val.PubKey[:]) - pkey, err := cryptoAmino.PubKeyFromBytes(validatorPubKey.Bytes()) - if err != nil { - panic(err) - } - - vals[i] = abciTypes.ValidatorUpdate{ - PubKey: types2.TM2PB.PubKey(pkey), - Power: big.NewInt(0).Div(big.NewInt(0).Mul(helpers.StringToBigInt(val.TotalBipStake), - big.NewInt(100000000)), totalPower).Int64(), - } - } + vals := app.updateValidators(0) app.appDB.SetStartHeight(genesisState.StartHeight) app.appDB.SaveValidators(vals) rewards.SetStartHeight(genesisState.StartHeight) - validators.SetStartHeight(genesisState.StartHeight) return abciTypes.ResponseInitChain{ Validators: vals, } } -// Signals the beginning of a block. +// BeginBlock signals the beginning of a block. func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.ResponseBeginBlock { height := uint64(req.Header.Height) - app.StatisticData().PushStartBlock(statistics.StartRequest{Height: int64(height), Now: time.Now(), HeaderTime: req.Header.Time}) - - if app.haltHeight > 0 && height >= app.haltHeight { - panic(fmt.Sprintf("Application halted at height %d", height)) - } - - if upgrades.IsUpgradeBlock(height) { - var err error - app.stateDeliver, err = state.NewState(app.height, app.stateDB, app.eventsDB, app.cfg.KeepLastStates, app.cfg.StateCacheSize) - if err != nil { - panic(err) - } - app.stateCheck = state.NewCheckState(app.stateDeliver) - } + app.StatisticData().PushStartBlock(&statistics.StartRequest{Height: int64(height), Now: time.Now(), HeaderTime: req.Header.Time}) app.stateDeliver.Lock() @@ -213,6 +173,10 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res } app.lock.Unlock() + if app.isApplicationHalted(height) { + panic(fmt.Sprintf("Application halted at height %d", height)) + } + // give penalty to Byzantine validators for _, byzVal := range req.ByzantineValidators { var address types.TmAddress @@ -224,7 +188,7 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res continue } - app.stateDeliver.FrozenFunds.PunishFrozenFundsWithAddress(height, height+candidates.UnbondPeriod, address) + app.stateDeliver.FrozenFunds.PunishFrozenFundsWithID(height, height+candidates.UnbondPeriod, candidate.ID) app.stateDeliver.Validators.PunishByzantineValidator(address) app.stateDeliver.Candidates.PunishByzantineCandidate(height, address) } @@ -233,10 +197,10 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res frozenFunds := app.stateDeliver.FrozenFunds.GetFrozenFunds(uint64(req.Header.Height)) if frozenFunds != nil { for _, item := range frozenFunds.List { - app.eventsDB.AddEvent(uint32(req.Header.Height), eventsdb.UnbondEvent{ + app.eventsDB.AddEvent(uint32(req.Header.Height), &eventsdb.UnbondEvent{ Address: item.Address, Amount: item.Value.String(), - Coin: item.Coin, + Coin: uint64(item.Coin), ValidatorPubKey: *item.CandidateKey, }) app.stateDeliver.Accounts.AddBalance(item.Address, item.Coin, item.Value) @@ -246,19 +210,15 @@ func (app *Blockchain) BeginBlock(req abciTypes.RequestBeginBlock) abciTypes.Res app.stateDeliver.FrozenFunds.Delete(frozenFunds.Height()) } + app.stateDeliver.Halts.Delete(height) + return abciTypes.ResponseBeginBlock{} } -// Signals the end of a block, returns changes to the validator set +// EndBlock signals the end of a block, returns changes to the validator set func (app *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes.ResponseEndBlock { height := uint64(req.Height) - if height == upgrades.UpgradeBlock3 { - ApplyUpgrade3(app.stateDeliver, app.eventsDB) - } - - var updates []abciTypes.ValidatorUpdate - vals := app.stateDeliver.Validators.GetValidators() hasDroppedValidators := false @@ -290,7 +250,7 @@ func (app *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes.Respons // accumulate rewards reward := rewards.GetRewardForBlock(height) - app.stateDeliver.Checker.AddCoinVolume(types.GetBaseCoin(), reward) + app.stateDeliver.Checker.AddCoinVolume(types.GetBaseCoinID(), reward) reward.Add(reward, app.rewards) // compute remainder to keep total emission consist @@ -318,83 +278,95 @@ func (app *Blockchain) EndBlock(req abciTypes.RequestEndBlock) abciTypes.Respons app.stateDeliver.Validators.PayRewards(height) } + hasChangedPublicKeys := false + if app.stateDeliver.Candidates.IsChangedPublicKeys() { + app.stateDeliver.Candidates.ResetIsChangedPublicKeys() + hasChangedPublicKeys = true + } + // update validators - if req.Height%120 == 0 || hasDroppedValidators { - app.stateDeliver.Candidates.RecalculateStakes(height) + var updates []abciTypes.ValidatorUpdate + if req.Height%120 == 0 || hasDroppedValidators || hasChangedPublicKeys { + updates = app.updateValidators(height) + } - valsCount := validators.GetValidatorsCountForBlock(height) - newCandidates := app.stateDeliver.Candidates.GetNewCandidates(valsCount) - if len(newCandidates) < valsCount { - valsCount = len(newCandidates) - } + defer func() { + app.StatisticData().PushEndBlock(&statistics.EndRequest{TimeEnd: time.Now(), Height: int64(app.height)}) + }() + + return abciTypes.ResponseEndBlock{ + ValidatorUpdates: updates, + ConsensusParamUpdates: &abciTypes.ConsensusParams{ + Block: &abciTypes.BlockParams{ + MaxBytes: blockMaxBytes, + MaxGas: int64(app.stateDeliver.App.GetMaxGas()), + }, + }, + } +} - newValidators := make([]abciTypes.ValidatorUpdate, valsCount) +func (app *Blockchain) updateValidators(height uint64) []abciTypes.ValidatorUpdate { + app.stateDeliver.Candidates.RecalculateStakes(height) - // calculate total power - totalPower := big.NewInt(0) - for _, candidate := range newCandidates { - totalPower.Add(totalPower, app.stateDeliver.Candidates.GetTotalStake(candidate.PubKey)) - } + valsCount := validators.GetValidatorsCountForBlock(height) + newCandidates := app.stateDeliver.Candidates.GetNewCandidates(valsCount) + if len(newCandidates) < valsCount { + valsCount = len(newCandidates) + } - for i := range newCandidates { - power := big.NewInt(0).Div(big.NewInt(0).Mul(app.stateDeliver.Candidates.GetTotalStake(newCandidates[i].PubKey), - big.NewInt(100000000)), totalPower).Int64() + newValidators := make([]abciTypes.ValidatorUpdate, valsCount) - if power == 0 { - power = 1 - } + // calculate total power + totalPower := big.NewInt(0) + for _, candidate := range newCandidates { + totalPower.Add(totalPower, app.stateDeliver.Candidates.GetTotalStake(candidate.PubKey)) + } - newValidators[i] = abciTypes.Ed25519ValidatorUpdate(newCandidates[i].PubKey[:], power) + for i := range newCandidates { + power := big.NewInt(0).Div(big.NewInt(0).Mul(app.stateDeliver.Candidates.GetTotalStake(newCandidates[i].PubKey), + big.NewInt(100000000)), totalPower).Int64() + + if power == 0 { + power = 1 } - sort.SliceStable(newValidators, func(i, j int) bool { - return newValidators[i].Power > newValidators[j].Power - }) + newValidators[i] = abciTypes.Ed25519ValidatorUpdate(newCandidates[i].PubKey[:], power) + } - // update validators in state - app.stateDeliver.Validators.SetNewValidators(newCandidates) + sort.SliceStable(newValidators, func(i, j int) bool { + return newValidators[i].Power > newValidators[j].Power + }) - activeValidators := app.getCurrentValidators() + // update validators in state + app.stateDeliver.Validators.SetNewValidators(newCandidates) - app.saveCurrentValidators(newValidators) + activeValidators := app.getCurrentValidators() - updates = newValidators + app.saveCurrentValidators(newValidators) - for _, validator := range activeValidators { - persisted := false - for _, newValidator := range newValidators { - if bytes.Equal(validator.PubKey.Data, newValidator.PubKey.Data) { - persisted = true - break - } - } + updates := newValidators - // remove validator - if !persisted { - updates = append(updates, abciTypes.ValidatorUpdate{ - PubKey: validator.PubKey, - Power: 0, - }) + for _, validator := range activeValidators { + persisted := false + for _, newValidator := range newValidators { + if bytes.Equal(validator.PubKey.Data, newValidator.PubKey.Data) { + persisted = true + break } } - } - defer func() { - app.StatisticData().PushEndBlock(statistics.EndRequest{TimeEnd: time.Now(), Height: int64(app.height)}) - }() - - return abciTypes.ResponseEndBlock{ - ValidatorUpdates: updates, - ConsensusParamUpdates: &abciTypes.ConsensusParams{ - Block: &abciTypes.BlockParams{ - MaxBytes: BlockMaxBytes, - MaxGas: int64(app.stateDeliver.App.GetMaxGas()), - }, - }, + // remove validator + if !persisted { + updates = append(updates, abciTypes.ValidatorUpdate{ + PubKey: validator.PubKey, + Power: 0, + }) + } } + return updates } -// Return application info. Used for synchronization between Tendermint and Minter +// Info return application info. Used for synchronization between Tendermint and Minter func (app *Blockchain) Info(req abciTypes.RequestInfo) (resInfo abciTypes.ResponseInfo) { return abciTypes.ResponseInfo{ Version: version.Version, @@ -404,9 +376,9 @@ func (app *Blockchain) Info(req abciTypes.RequestInfo) (resInfo abciTypes.Respon } } -// Deliver a tx for full processing +// DeliverTx deliver a tx for full processing func (app *Blockchain) DeliverTx(req abciTypes.RequestDeliverTx) abciTypes.ResponseDeliverTx { - response := transaction.RunTx(app.stateDeliver, false, req.Tx, app.rewards, app.height, &sync.Map{}, 0) + response := transaction.RunTx(app.stateDeliver, req.Tx, app.rewards, app.height, &sync.Map{}, 0) return abciTypes.ResponseDeliverTx{ Code: response.Code, @@ -424,9 +396,9 @@ func (app *Blockchain) DeliverTx(req abciTypes.RequestDeliverTx) abciTypes.Respo } } -// Validate a tx for the mempool +// CheckTx validates a tx for the mempool func (app *Blockchain) CheckTx(req abciTypes.RequestCheckTx) abciTypes.ResponseCheckTx { - response := transaction.RunTx(app.stateCheck, true, req.Tx, nil, app.height, app.currentMempool, app.MinGasPrice()) + response := transaction.RunTx(app.stateCheck, req.Tx, nil, app.height, app.currentMempool, app.MinGasPrice()) return abciTypes.ResponseCheckTx{ Code: response.Code, @@ -459,103 +431,77 @@ func (app *Blockchain) Commit() abciTypes.ResponseCommit { } // Flush events db - _ = app.eventsDB.CommitEvents() + err = app.eventsDB.CommitEvents() + if err != nil { + panic(err) + } // Persist application hash and height app.appDB.SetLastBlockHash(hash) app.appDB.SetLastHeight(app.height) + app.stateDeliver.Unlock() + // Resetting check state to be consistent with current height app.resetCheckState() // Clear mempool app.currentMempool = &sync.Map{} - app.stateDeliver.Unlock() - return abciTypes.ResponseCommit{ Data: hash, } } -// Unused method, required by Tendermint +// Query Unused method, required by Tendermint func (app *Blockchain) Query(reqQuery abciTypes.RequestQuery) abciTypes.ResponseQuery { return abciTypes.ResponseQuery{} } -// Unused method, required by Tendermint +// SetOption Unused method, required by Tendermint func (app *Blockchain) SetOption(req abciTypes.RequestSetOption) abciTypes.ResponseSetOption { return abciTypes.ResponseSetOption{} } -// Gracefully stopping Minter Blockchain instance +// Stop gracefully stopping Minter Blockchain instance func (app *Blockchain) Stop() { app.appDB.Close() - app.stateDB.Close() + if err := app.stateDB.Close(); err != nil { + panic(err) + } } -// Get immutable state of Minter Blockchain -func (app *Blockchain) CurrentState() *state.State { +// CurrentState returns immutable state of Minter Blockchain +func (app *Blockchain) CurrentState() *state.CheckState { app.lock.RLock() defer app.lock.RUnlock() - return state.NewCheckState(app.stateCheck) + return app.stateCheck } -// Get immutable state of Minter Blockchain for given height -func (app *Blockchain) GetStateForHeight(height uint64) (*state.State, error) { - app.lock.RLock() - defer app.lock.RUnlock() - - if height != 0 { +// GetStateForHeight returns immutable state of Minter Blockchain for given height +func (app *Blockchain) GetStateForHeight(height uint64) (*state.CheckState, error) { + if height > 0 { s, err := state.NewCheckStateAtHeight(height, app.stateDB) if err != nil { - return nil, rpctypes.RPCError{Code: 404, Message: "State at given height not found", Data: err.Error()} + return nil, err } return s, nil } return blockchain.CurrentState(), nil } -func (app *Blockchain) MissedBlocks(pubKey string, height uint64) (missedBlocks string, missedBlocksCount int, err error) { - if !strings.HasPrefix(pubKey, "Mp") { - return "", 0, status.Error(codes.InvalidArgument, "public key don't has prefix 'Mp'") - } - - cState, err := blockchain.GetStateForHeight(height) - if err != nil { - return "", 0, status.Error(codes.NotFound, err.Error()) - } - - if height != 0 { - cState.Lock() - cState.Validators.LoadValidators() - cState.Unlock() - } - - cState.RLock() - defer cState.RUnlock() - - val := cState.Validators.GetByPublicKey(types.HexToPubkey(pubKey)) - if val == nil { - return "", 0, status.Error(codes.NotFound, "Validator not found") - } - - return val.AbsentTimes.String(), val.CountAbsentTimes(), nil - -} - -// Get current height of Minter Blockchain +// Height returns current height of Minter Blockchain func (app *Blockchain) Height() uint64 { return atomic.LoadUint64(&app.height) } -// Set Tendermint node +// SetTmNode sets Tendermint node func (app *Blockchain) SetTmNode(node *tmNode.Node) { app.tmNode = node } -// Get minimal acceptable gas price +// MinGasPrice returns minimal acceptable gas price func (app *Blockchain) MinGasPrice() uint32 { mempoolSize := app.tmNode.Mempool().Size() @@ -612,10 +558,12 @@ func (app *Blockchain) updateBlocksTimeDelta(height uint64, count int64) { app.appDB.SetLastBlocksTimeDelta(height, delta) } +// SetBlocksTimeDelta sets current blocks time delta func (app *Blockchain) SetBlocksTimeDelta(height uint64, value int) { app.appDB.SetLastBlocksTimeDelta(height, value) } +// GetBlocksTimeDelta returns current blocks time delta func (app *Blockchain) GetBlocksTimeDelta(height, count uint64) (int, error) { return app.appDB.GetLastBlocksTimeDelta(height) } @@ -626,11 +574,11 @@ func (app *Blockchain) calcMaxGas(height uint64) uint64 { // skip first 20 blocks if height <= 20 { - return DefaultMaxGas + return defaultMaxGas } // get current max gas - newMaxGas := app.stateCheck.App.GetMaxGas() + newMaxGas := app.stateCheck.App().GetMaxGas() // check if blocks are created in time if delta, _ := app.GetBlocksTimeDelta(height, blockDelta); delta > targetTime*blockDelta { @@ -640,45 +588,93 @@ func (app *Blockchain) calcMaxGas(height uint64) uint64 { } // check if max gas is too high - if newMaxGas > DefaultMaxGas { - newMaxGas = DefaultMaxGas + if newMaxGas > defaultMaxGas { + newMaxGas = defaultMaxGas } // check if max gas is too low - if newMaxGas < MinMaxGas { - newMaxGas = MinMaxGas + if newMaxGas < minMaxGas { + newMaxGas = minMaxGas } return newMaxGas } +// GetEventsDB returns current EventsDB func (app *Blockchain) GetEventsDB() eventsdb.IEventsDB { return app.eventsDB } +// SetStatisticData used for collection statistics about blockchain operations func (app *Blockchain) SetStatisticData(statisticData *statistics.Data) *statistics.Data { app.statisticData = statisticData return app.statisticData } +// StatisticData used for collection statistics about blockchain operations func (app *Blockchain) StatisticData() *statistics.Data { return app.statisticData } +// GetValidatorStatus returns given validator's status func (app *Blockchain) GetValidatorStatus(address types.TmAddress) int8 { app.lock.RLock() defer app.lock.RUnlock() + return app.validatorsStatuses[address] } -func (app *Blockchain) MaxPeerHeight() int64 { - var max int64 - for _, peer := range app.tmNode.Switch().Peers().List() { - height := peer.Get(types2.PeerStateKey).(evidence.PeerState).GetHeight() - if height > max { - max = height + +// DeleteStateVersions deletes states in given range +func (app *Blockchain) DeleteStateVersions(from, to int64) error { + app.lock.RLock() + defer app.lock.RUnlock() + + app.stateDeliver.Tree().GlobalLock() + defer app.stateDeliver.Tree().GlobalUnlock() + + return app.stateDeliver.Tree().DeleteVersionsIfExists(from, to) +} + +func (app *Blockchain) isApplicationHalted(height uint64) bool { + if app.haltHeight > 0 && height >= app.haltHeight { + return true + } + + halts := app.stateDeliver.Halts.GetHaltBlocks(height) + if halts != nil { + // calculate total power of validators + vals := app.stateDeliver.Validators.GetValidators() + totalPower, totalVotedPower := big.NewInt(0), big.NewInt(0) + for _, val := range vals { + // skip if candidate is not present + if val.IsToDrop() || app.validatorsStatuses[val.GetAddress()] != ValidatorPresent { + continue + } + + for _, halt := range halts.List { + if halt.Pubkey == val.PubKey { + totalVotedPower.Add(totalVotedPower, val.GetTotalBipStake()) + } + } + + totalPower.Add(totalPower, val.GetTotalBipStake()) + } + + if totalPower.Cmp(types.Big0) == 0 { + totalPower = big.NewInt(1) + } + + votingResult := new(big.Float).Quo( + new(big.Float).SetInt(totalVotedPower), + new(big.Float).SetInt(totalPower), + ) + + if votingResult.Cmp(big.NewFloat(votingPowerConsensus)) == 1 { + return true } } - return max + + return false } func getDbOpts(memLimit int) *opt.Options { diff --git a/core/minter/minter_test.go b/core/minter/minter_test.go index 880e214fa..f8de2b1a8 100644 --- a/core/minter/minter_test.go +++ b/core/minter/minter_test.go @@ -10,57 +10,150 @@ import ( "github.com/MinterTeam/minter-go-node/cmd/utils" "github.com/MinterTeam/minter-go-node/config" "github.com/MinterTeam/minter-go-node/core/developers" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" candidates2 "github.com/MinterTeam/minter-go-node/core/state/candidates" + "github.com/MinterTeam/minter-go-node/core/statistics" "github.com/MinterTeam/minter-go-node/core/transaction" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/legacy/candidates" "github.com/MinterTeam/minter-go-node/log" "github.com/MinterTeam/minter-go-node/rlp" "github.com/tendermint/go-amino" - tmConfig "github.com/tendermint/tendermint/config" log2 "github.com/tendermint/tendermint/libs/log" tmos "github.com/tendermint/tendermint/libs/os" tmNode "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/privval" "github.com/tendermint/tendermint/proxy" - rpc "github.com/tendermint/tendermint/rpc/client" - _ "github.com/tendermint/tendermint/types" + rpc "github.com/tendermint/tendermint/rpc/client/local" types2 "github.com/tendermint/tendermint/types" "math/big" + "math/rand" "os" - "path/filepath" - "sync" + "strconv" "testing" "time" ) -var pv *privval.FilePV -var cfg *tmConfig.Config -var tmCli *rpc.Local -var app *Blockchain -var privateKey *ecdsa.PrivateKey -var l sync.Mutex -var nonce = uint64(1) - -func init() { - l.Lock() - go initNode() - l.Lock() +func getPrivateKey() *ecdsa.PrivateKey { + b, _ := hex.DecodeString("825ca965c34ef1c8343e8e377959108370c23ba6194d858452b63432456403f9") + privateKey, _ := crypto.ToECDSA(b) + return privateKey +} + +func makeTestValidatorsAndCandidates(pubkeys []string, stake *big.Int) ([]types.Validator, []types.Candidate) { + vals := make([]types.Validator, 0, len(pubkeys)) + cands := make([]types.Candidate, 0, len(pubkeys)) + + for i, val := range pubkeys { + pkeyBytes, err := base64.StdEncoding.DecodeString(val) + if err != nil { + panic(err) + } + + var pkey types.Pubkey + copy(pkey[:], pkeyBytes) + addr := developers.Address + + vals = append(vals, types.Validator{ + TotalBipStake: stake.String(), + PubKey: pkey, + AccumReward: big.NewInt(0).String(), + AbsentTimes: types.NewBitArray(24), + }) + + cands = append(cands, types.Candidate{ + ID: uint64(i) + 1, + RewardAddress: addr, + OwnerAddress: crypto.PubkeyToAddress(getPrivateKey().PublicKey), + ControlAddress: addr, + TotalBipStake: stake.String(), + PubKey: pkey, + Commission: 100, + Stakes: []types.Stake{ + { + Owner: addr, + Coin: uint64(types.GetBaseCoinID()), + Value: stake.String(), + BipValue: stake.String(), + }, + }, + Status: candidates2.CandidateStatusOnline, + }) + } + + return vals, cands } -func initNode() { - utils.MinterHome = os.ExpandEnv(filepath.Join("$HOME", ".minter_test")) - _ = os.RemoveAll(utils.MinterHome) +func getTestGenesis(pv *privval.FilePV) func() (*types2.GenesisDoc, error) { + return func() (*types2.GenesisDoc, error) { + + appHash := [32]byte{} + + validators, candidates := makeTestValidatorsAndCandidates([]string{base64.StdEncoding.EncodeToString(pv.Key.PubKey.Bytes()[5:])}, helpers.BipToPip(big.NewInt(1000))) + + appState := types.AppState{ + TotalSlashed: "0", + Accounts: []types.Account{ + { + Address: crypto.PubkeyToAddress(getPrivateKey().PublicKey), + Balance: []types.Balance{ + { + Coin: uint64(types.GetBaseCoinID()), + Value: helpers.BipToPip(big.NewInt(1000000)).String(), + }, + }, + }, + }, + Validators: validators, + Candidates: candidates, + } + + appStateJSON, err := amino.MarshalJSON(appState) + if err != nil { + return nil, err + } + + genesisDoc := types2.GenesisDoc{ + ChainID: "minter-test-network", + GenesisTime: time.Now(), + AppHash: appHash[:], + AppState: json.RawMessage(appStateJSON), + } + + err = genesisDoc.ValidateAndComplete() + if err != nil { + return nil, err + } + + genesisFile := utils.GetMinterHome() + "/config/genesis.json" + if err := genesisDoc.SaveAs(genesisFile); err != nil { + panic(err) + } + + return &genesisDoc, nil + } +} + +var port = 0 + +func getPort() string { + port++ + return strconv.Itoa(port) +} + +func initTestNode(t *testing.T) (*Blockchain, *rpc.Local, *privval.FilePV) { + utils.MinterHome = t.TempDir() if err := tmos.EnsureDir(utils.GetMinterHome()+"/tmdata/blockstore.db", 0777); err != nil { - panic(err.Error()) + t.Fatal(err) } minterCfg := config.GetConfig() logger := log.NewLogger(minterCfg) - cfg = config.GetTmConfig(minterCfg) + cfg := config.GetTmConfig(minterCfg) cfg.Consensus.TimeoutPropose = 0 cfg.Consensus.TimeoutPrecommit = 0 cfg.Consensus.TimeoutPrevote = 0 @@ -70,21 +163,18 @@ func initNode() { cfg.Consensus.TimeoutProposeDelta = 0 cfg.Consensus.SkipTimeoutCommit = true cfg.RPC.ListenAddress = "" - cfg.P2P.ListenAddress = "0.0.0.0:25566" + cfg.P2P.ListenAddress = "0.0.0.0:2556" + getPort() // todo cfg.P2P.Seeds = "" cfg.P2P.PersistentPeers = "" cfg.DBBackend = "memdb" - pv = privval.GenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) + pv := privval.GenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()) pv.Save() - b, _ := hex.DecodeString("825ca965c34ef1c8343e8e377959108370c23ba6194d858452b63432456403f9") - privateKey, _ = crypto.ToECDSA(b) - - app = NewMinterBlockchain(minterCfg) + app := NewMinterBlockchain(minterCfg) nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile()) if err != nil { - panic(err) + t.Fatal(err) } node, err := tmNode.NewNode( @@ -92,58 +182,108 @@ func initNode() { pv, nodeKey, proxy.NewLocalClientCreator(app), - getGenesis, + getTestGenesis(pv), tmNode.DefaultDBProvider, tmNode.DefaultMetricsProvider(cfg.Instrumentation), log2.NewTMLogger(os.Stdout), ) if err != nil { - panic(fmt.Sprintf("Failed to create a node: %v", err)) + t.Fatal(fmt.Sprintf("Failed to create a node: %v", err)) } if err = node.Start(); err != nil { - panic(fmt.Sprintf("Failed to start node: %v", err)) + t.Fatal(fmt.Sprintf("Failed to start node: %v", err)) } logger.Info("Started node", "nodeInfo", node.Switch().NodeInfo()) app.SetTmNode(node) - tmCli = rpc.NewLocal(node) - l.Unlock() -} -func TestBlocksCreation(t *testing.T) { - // Wait for blocks - blocks, err := tmCli.Subscribe(context.TODO(), "test-client", "tm.event = 'NewBlock'") + tmCli := rpc.New(blockchain.tmNode) + + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") if err != nil { - panic(err) + t.Fatal(err) } select { case <-blocks: - // got block + err = tmCli.UnsubscribeAll(context.Background(), "test-client") + if err != nil { + t.Fatal(err) + } case <-time.After(10 * time.Second): - t.Fatalf("Timeout waiting for the first block") + t.Fatal("Timeout waiting for the first block") } - err = tmCli.UnsubscribeAll(context.TODO(), "test-client") + return app, tmCli, pv +} + +func TestBlockchain_Height(t *testing.T) { + blockchain, tmCli, _ := initTestNode(t) + defer blockchain.Stop() + + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") if err != nil { - panic(err) + t.Fatal(err) + } + + block := <-blocks + if block.Data.(types2.EventDataNewBlock).Block.Height != int64(blockchain.Height()) { + t.Fatal("invalid blockchain height") + } + + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) } } -func TestSendTx(t *testing.T) { - for blockchain.Height() < 2 { - time.Sleep(time.Millisecond) +func TestBlockchain_SetStatisticData(t *testing.T) { + blockchain, tmCli, _ := initTestNode(t) + defer blockchain.Stop() + + ch := make(chan struct{}) + blockchain.stateDeliver.Lock() + go func() { + close(ch) + blockchain.SetStatisticData(statistics.New()).Statistic(context.Background()) + }() + <-ch + time.Sleep(time.Second) + blockchain.stateDeliver.Unlock() + + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") + if err != nil { + t.Fatal(err) + } + + <-blocks + <-blocks + <-blocks + block := <-blocks + if block.Data.(types2.EventDataNewBlock).Block.Header.Time.Nanosecond() != blockchain.StatisticData().BlockEnd.LastBlockInfo.HeaderTimestamp.Nanosecond() { + t.Fatal("statistic last block and event event last block header time not equal") } - value := helpers.BipToPip(big.NewInt(10)) - to := types.Address([20]byte{1}) + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() - data := transaction.SendData{ - Coin: types.GetBaseCoin(), - To: to, - Value: value, + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) + } +} + +func TestBlockchain_IsApplicationHalted(t *testing.T) { + blockchain, tmCli, pv := initTestNode(t) + defer blockchain.Stop() + + data := transaction.SetHaltBlockData{ + PubKey: types.BytesToPubkey(pv.Key.PubKey.Bytes()[5:]), + Height: 5, } encodedData, err := rlp.EncodeToBytes(data) @@ -152,63 +292,67 @@ func TestSendTx(t *testing.T) { } tx := transaction.Transaction{ - Nonce: nonce, + Nonce: 1, ChainID: types.CurrentChainID, GasPrice: 1, - GasCoin: types.GetBaseCoin(), - Type: transaction.TypeSend, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeSetHaltBlock, Data: encodedData, SignatureType: transaction.SigTypeSingle, } - nonce++ - if err := tx.Sign(privateKey); err != nil { + if err := tx.Sign(getPrivateKey()); err != nil { t.Fatal(err) } txBytes, _ := tx.Serialize() - res, err := tmCli.BroadcastTxSync(txBytes) if err != nil { t.Fatalf("Failed: %s", err.Error()) } - if res.Code != 0 { - t.Fatalf("CheckTx code is not 0: %d", res.Code) + t.Fatalf("CheckTx code is not 0: %d, %s", res.Code, res.Log) } - txs, err := tmCli.Subscribe(context.TODO(), "test-client", "tm.event = 'Tx'") + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") if err != nil { - panic(err) + t.Fatal(err) } - select { - case <-txs: - // got tx - case <-time.After(10 * time.Second): - t.Fatalf("Timeout waiting for the tx to be committed") - } + for { + select { + case block := <-blocks: + height := block.Data.(types2.EventDataNewBlock).Block.Height + if height < int64(data.Height) { + continue + } - err = tmCli.UnsubscribeAll(context.TODO(), "test-client") - if err != nil { - panic(err) + t.Fatalf("don't stop on block %d", height) + return + case <-time.After(2 * time.Second): + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) + } + return + } } } -// TODO: refactor -func TestSmallStakeValidator(t *testing.T) { - for blockchain.Height() < 2 { - time.Sleep(time.Millisecond) - } +func TestBlockchain_GetStateForHeightAndDeleteStateVersions(t *testing.T) { + blockchain, tmCli, _ := initTestNode(t) + defer blockchain.Stop() - pubkey := types.Pubkey{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - - data := transaction.DeclareCandidacyData{ - Address: crypto.PubkeyToAddress(privateKey.PublicKey), - PubKey: pubkey, - Commission: 10, - Coin: types.GetBaseCoin(), - Stake: big.NewInt(0), + symbol := types.StrToCoinSymbol("AAA123") + data := transaction.CreateCoinData{ + Name: "nAAA123", + Symbol: symbol, + InitialAmount: helpers.BipToPip(big.NewInt(1000000)), + InitialReserve: helpers.BipToPip(big.NewInt(10000)), + ConstantReserveRatio: 70, + MaxSupply: big.NewInt(0).Exp(big.NewInt(10), big.NewInt(15+18), nil), } encodedData, err := rlp.EncodeToBytes(data) @@ -217,257 +361,457 @@ func TestSmallStakeValidator(t *testing.T) { } tx := transaction.Transaction{ - Nonce: nonce, + Nonce: 1, ChainID: types.CurrentChainID, GasPrice: 1, - GasCoin: types.GetBaseCoin(), - Type: transaction.TypeDeclareCandidacy, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeCreateCoin, Data: encodedData, SignatureType: transaction.SigTypeSingle, } - nonce++ - if err := tx.Sign(privateKey); err != nil { + if err := tx.Sign(getPrivateKey()); err != nil { t.Fatal(err) } txBytes, _ := tx.Serialize() - res, err := tmCli.BroadcastTxSync(txBytes) + res, err := tmCli.BroadcastTxCommit(txBytes) if err != nil { t.Fatalf("Failed: %s", err.Error()) } - if res.Code != 0 { - t.Fatalf("CheckTx code is not 0: %d", res.Code) - } time.Sleep(time.Second) - setOnData := transaction.SetCandidateOnData{ - PubKey: pubkey, + resultTx, err := tmCli.Tx(res.Hash.Bytes(), false) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + + checkState := blockchain.CurrentState() + + if !checkState.Coins().ExistsBySymbol(symbol) { + t.Fatalf("Failed: %s", "state invalid") } - encodedData, err = rlp.EncodeToBytes(setOnData) + checkState, err = blockchain.GetStateForHeight(uint64(resultTx.Height - 1)) if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + + if checkState.Coins().ExistsBySymbol(symbol) { + t.Fatalf("Failed: %s", "state invalid") + } + + err = blockchain.DeleteStateVersions(0, resultTx.Height) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + + _, err = blockchain.GetStateForHeight(uint64(resultTx.Height - 1)) + if err == nil { + t.Fatalf("Failed: %s", "state not deleted") + } + + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { t.Fatal(err) } +} + +func TestBlockchain_SendTx(t *testing.T) { + blockchain, tmCli, _ := initTestNode(t) + defer blockchain.Stop() + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := transaction.SendData{ + Coin: types.GetBaseCoinID(), + To: to, + Value: value, + } - tx = transaction.Transaction{ + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + nonce := uint64(1) + tx := transaction.Transaction{ Nonce: nonce, - GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), - Type: transaction.TypeSetCandidateOnline, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeSend, Data: encodedData, SignatureType: transaction.SigTypeSingle, } nonce++ - if err := tx.Sign(privateKey); err != nil { + if err := tx.Sign(getPrivateKey()); err != nil { t.Fatal(err) } - txBytes, _ = tx.Serialize() - res, err = tmCli.BroadcastTxSync(txBytes) + txBytes, _ := tx.Serialize() + + res, err := tmCli.BroadcastTxSync(txBytes) if err != nil { t.Fatalf("Failed: %s", err.Error()) } + if res.Code != 0 { t.Fatalf("CheckTx code is not 0: %d", res.Code) } - status, _ := tmCli.Status() - targetBlockHeight := status.SyncInfo.LatestBlockHeight - (status.SyncInfo.LatestBlockHeight % 120) + 150 - println("target block", targetBlockHeight) - - blocks, err := tmCli.Subscribe(context.TODO(), "test-client", "tm.event = 'NewBlock'") + txs, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'Tx'") if err != nil { - panic(err) + t.Fatal(err) } - ready := false - for !ready { - select { - case block := <-blocks: - if block.Data.(types2.EventDataNewBlock).Block.Height < targetBlockHeight { - continue - } + select { + case <-txs: + // got tx + case <-time.After(10 * time.Second): + t.Fatalf("Timeout waiting for the tx to be committed") + } - vals, _ := tmCli.Validators(&targetBlockHeight, 1, 1000) + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) + } +} - if len(vals.Validators) > 1 { - t.Errorf("There are should be 1 validator (has %d)", len(vals.Validators)) - } +func TestBlockchain_FrozenFunds(t *testing.T) { + blockchain, tmCli, pv := initTestNode(t) - if len(app.stateDeliver.Validators.GetValidators()) > 1 { - t.Errorf("There are should be 1 validator (has %d)", len(app.stateDeliver.Validators.GetValidators())) - } + targetHeight := uint64(10) + value := helpers.BipToPip(big.NewInt(1000)) + pubkey := types.BytesToPubkey(pv.Key.PubKey.Bytes()[5:]) + blockchain.stateDeliver.RLock() + blockchain.stateDeliver.Candidates.SubStake(developers.Address, pubkey, 0, big.NewInt(0).Set(value)) + blockchain.stateDeliver.FrozenFunds.AddFund(targetHeight, developers.Address, pubkey, blockchain.stateDeliver.Candidates.ID(pubkey), 0, big.NewInt(0).Set(value)) + blockchain.stateDeliver.RUnlock() - ready = true - case <-time.After(10 * time.Second): - t.Fatalf("Timeout waiting for the block") - } - } - err = tmCli.UnsubscribeAll(context.TODO(), "test-client") + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") if err != nil { - panic(err) + t.Fatal(err) } - time.Sleep(time.Second) + for block := range blocks { + if block.Data.(types2.EventDataNewBlock).Block.Height < int64(targetHeight) { + continue + } + break + } - encodedData, err = rlp.EncodeToBytes(setOnData) - if err != nil { + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { t.Fatal(err) } - tx = transaction.Transaction{ - Nonce: nonce, - GasPrice: 1, - ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), - Type: transaction.TypeSetCandidateOnline, - Data: encodedData, - SignatureType: transaction.SigTypeSingle, + events := blockchain.GetEventsDB().LoadEvents(uint32(targetHeight)) + + if len(events) == 0 { + t.Errorf("empty events for %d block", targetHeight) } - nonce++ + if events[0].Type() != eventsdb.TypeUnbondEvent { + t.Fatal("event is not UnbondEvent") + } + if events[0].AddressString() != developers.Address.String() { + t.Error("event address invalid") + } + if events[0].ValidatorPubKeyString() != pubkey.String() { + t.Error("event validator pubkey invalid") + } + +} - if err := tx.Sign(privateKey); err != nil { +func TestBlockchain_RecalculateStakes_andRemoveValidator(t *testing.T) { + blockchain, tmCli, pv := initTestNode(t) + defer blockchain.Stop() + + txs, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'Tx'") + if err != nil { t.Fatal(err) } + symbol := types.StrToCoinSymbol("AAA123") + nonce := uint64(1) + { + data := transaction.CreateCoinData{ + Name: "nAAA123", + Symbol: symbol, + InitialAmount: helpers.BipToPip(big.NewInt(1000000)), + InitialReserve: helpers.BipToPip(big.NewInt(10000)), + ConstantReserveRatio: 70, + MaxSupply: big.NewInt(0).Exp(big.NewInt(10), big.NewInt(15+18), nil), + } - txBytes, _ = tx.Serialize() - res, err = tmCli.BroadcastTxSync(txBytes) - if err != nil { - t.Fatalf("Failed: %s", err.Error()) + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := transaction.Transaction{ + Nonce: nonce, + ChainID: types.CurrentChainID, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeCreateCoin, + Data: encodedData, + SignatureType: transaction.SigTypeSingle, + } + + if err := tx.Sign(getPrivateKey()); err != nil { + t.Fatal(err) + } + + txBytes, _ := tx.Serialize() + res, err := tmCli.BroadcastTxSync(txBytes) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + if res.Code != 0 { + t.Fatalf("CheckTx code is not 0: %d", res.Code) + } } - if res.Code != 0 { - t.Fatalf("CheckTx code is not 0: %d", res.Code) + <-txs + nonce++ + blockchain.lock.RLock() + coinID := blockchain.CurrentState().Coins().GetCoinBySymbol(symbol, 0).ID() + blockchain.lock.RUnlock() + { + buyCoinData := transaction.BuyCoinData{ + CoinToBuy: coinID, + ValueToBuy: helpers.BipToPip(big.NewInt(10000000)), + CoinToSell: 0, + MaximumValueToSell: helpers.BipToPip(big.NewInt(10000000000000000)), + } + + encodedData, err := rlp.EncodeToBytes(buyCoinData) + if err != nil { + t.Fatal(err) + } + + tx := transaction.Transaction{ + Nonce: nonce, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeBuyCoin, + Data: encodedData, + SignatureType: transaction.SigTypeSingle, + } + + if err := tx.Sign(getPrivateKey()); err != nil { + t.Fatal(err) + } + + txBytes, _ := tx.Serialize() + res, err := tmCli.BroadcastTxSync(txBytes) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + if res.Code != 0 { + t.Fatalf("CheckTx code is not 0: %d", res.Code) + } } + <-txs + nonce++ + { + delegateData := transaction.DelegateData{ + PubKey: types.BytesToPubkey(pv.Key.PubKey.Bytes()[5:]), + Coin: coinID, + Value: helpers.BipToPip(big.NewInt(9000000)), + } + + encodedData, err := rlp.EncodeToBytes(delegateData) + if err != nil { + t.Fatal(err) + } - status, _ = tmCli.Status() - targetBlockHeight = status.SyncInfo.LatestBlockHeight - (status.SyncInfo.LatestBlockHeight % 120) + 120 + 5 - println("target block", targetBlockHeight) + tx := transaction.Transaction{ + Nonce: nonce, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeDelegate, + Data: encodedData, + SignatureType: transaction.SigTypeSingle, + } - blocks, err = tmCli.Subscribe(context.TODO(), "test-client", "tm.event = 'NewBlock'") - if err != nil { - panic(err) + if err := tx.Sign(getPrivateKey()); err != nil { + t.Fatal(err) + } + + txBytes, _ := tx.Serialize() + res, err := tmCli.BroadcastTxSync(txBytes) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + if res.Code != 0 { + t.Fatalf("CheckTx code is not 0: %d", res.Code) + } } + <-txs + nonce++ + { + data := transaction.DeclareCandidacyData{ + Address: types.Address{1}, + PubKey: types.Pubkey{1}, + Commission: 10, + Coin: 0, + Stake: big.NewInt(10000), + } -FORLOOP2: - for { - select { - case block := <-blocks: - if block.Data.(types2.EventDataNewBlock).Block.Height < targetBlockHeight { - continue FORLOOP2 - } + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } - vals, _ := tmCli.Validators(&targetBlockHeight, 1, 100) + tx := transaction.Transaction{ + Nonce: nonce, + ChainID: types.CurrentChainID, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeDeclareCandidacy, + Data: encodedData, + SignatureType: transaction.SigTypeSingle, + } - if len(vals.Validators) > 1 { - t.Errorf("There should be only 1 validator, got %d", len(vals.Validators)) - } + if err := tx.Sign(getPrivateKey()); err != nil { + t.Fatal(err) + } - mvals := app.stateDeliver.Validators.GetValidators() - if len(mvals) > 1 { - t.Errorf("There should be only 1 validator, got %d", len(mvals)) - } + txBytes, _ := tx.Serialize() - break FORLOOP2 - case <-time.After(10 * time.Second): - t.Fatalf("Timeout waiting for the block") + res, err := tmCli.BroadcastTxSync(txBytes) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) } - } - err = tmCli.UnsubscribeAll(context.TODO(), "test-client") - if err != nil { - panic(err) + if res.Code != 0 { + t.Fatalf("CheckTx code is not 0: %d, %s", res.Code, res.Log) + } } -} + <-txs + nonce++ + { + data := transaction.SetCandidateOnData{ + PubKey: types.Pubkey{1}, + } -func getGenesis() (*types2.GenesisDoc, error) { - appHash := [32]byte{} + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } - validators, candidates := makeValidatorsAndCandidates([]string{base64.StdEncoding.EncodeToString(pv.Key.PubKey.Bytes()[5:])}, big.NewInt(10000000)) + tx := transaction.Transaction{ + Nonce: nonce, + ChainID: types.CurrentChainID, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + Type: transaction.TypeSetCandidateOnline, + Data: encodedData, + SignatureType: transaction.SigTypeSingle, + } - appState := types.AppState{ - TotalSlashed: "0", - Accounts: []types.Account{ - { - Address: crypto.PubkeyToAddress(privateKey.PublicKey), - Balance: []types.Balance{ - { - Coin: types.GetBaseCoin(), - Value: helpers.BipToPip(big.NewInt(1000000)).String(), - }, - }, - }, - }, - Validators: validators, - Candidates: candidates, + if err := tx.Sign(getPrivateKey()); err != nil { + t.Fatal(err) + } + + txBytes, _ := tx.Serialize() + + res, err := tmCli.BroadcastTxSync(txBytes) + if err != nil { + t.Fatalf("Failed: %s", err.Error()) + } + + if res.Code != 0 { + t.Fatalf("CheckTx code is not 0: %d, %s", res.Code, res.Log) + } } + <-txs - appStateJSON, err := amino.MarshalJSON(appState) + err = tmCli.Unsubscribe(context.Background(), "test-client", "tm.event = 'Tx'") if err != nil { - return nil, err + t.Fatal(err) } - genesisDoc := types2.GenesisDoc{ - ChainID: "minter-test-network", - GenesisTime: time.Now(), - AppHash: appHash[:], - AppState: json.RawMessage(appStateJSON), + blocks, err := tmCli.Subscribe(context.Background(), "test-client", "tm.event = 'NewBlock'") + if err != nil { + t.Fatal(err) } - err = genesisDoc.ValidateAndComplete() - if err != nil { - return nil, err + targetHeight := int64(123 + 12) // 12 = ValidatorMaxAbsentTimes + for block := range blocks { + if block.Data.(types2.EventDataNewBlock).Block.Height <= targetHeight { + continue + } + return + } + blockchain.lock.RLock() + candidate := blockchain.CurrentState().Candidates().GetCandidate(types.Pubkey{1}) + if candidate == nil { + t.Fatal("candidate not found") } + blockchain.lock.RUnlock() - genesisFile := utils.GetMinterHome() + "/config/genesis.json" - if err := genesisDoc.SaveAs(genesisFile); err != nil { - panic(err) + if candidate.Status == candidates.CandidateStatusOnline { + t.Fatal("candidate not Offline") } - return &genesisDoc, nil + blockchain.lock.RLock() + defer blockchain.lock.RUnlock() + + exportedState := blockchain.CurrentState().Export(blockchain.Height() - 1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) + } } -func makeValidatorsAndCandidates(pubkeys []string, stake *big.Int) ([]types.Validator, []types.Candidate) { - validators := make([]types.Validator, len(pubkeys)) - candidates := make([]types.Candidate, len(pubkeys)) - addr := developers.Address +func TestStopNetworkByHaltBlocks(t *testing.T) { + blockchain, _, _ := initTestNode(t) + defer blockchain.Stop() - for i, val := range pubkeys { - pkeyBytes, err := base64.StdEncoding.DecodeString(val) - if err != nil { - panic(err) - } + haltHeight := uint64(50) - var pkey types.Pubkey - copy(pkey[:], pkeyBytes) + v1Pubkey := [32]byte{} + v2Pubkey := [32]byte{} + v3Pubkey := [32]byte{} - validators[i] = types.Validator{ - TotalBipStake: stake.String(), - PubKey: pkey, - AccumReward: big.NewInt(0).String(), - AbsentTimes: types.NewBitArray(24), - } + rand.Read(v1Pubkey[:]) + rand.Read(v2Pubkey[:]) + rand.Read(v3Pubkey[:]) - candidates[i] = types.Candidate{ - RewardAddress: addr, - OwnerAddress: addr, - TotalBipStake: big.NewInt(1).String(), - PubKey: pkey, - Commission: 100, - Stakes: []types.Stake{ - { - Owner: addr, - Coin: types.GetBaseCoin(), - Value: stake.String(), - BipValue: stake.String(), - }, - }, - Status: candidates2.CandidateStatusOnline, - } + blockchain.stateDeliver.Validators.Create(v1Pubkey, helpers.BipToPip(big.NewInt(3))) + blockchain.stateDeliver.Validators.Create(v2Pubkey, helpers.BipToPip(big.NewInt(5))) + blockchain.stateDeliver.Validators.Create(v3Pubkey, helpers.BipToPip(big.NewInt(3))) + + v1Address := blockchain.stateDeliver.Validators.GetValidators()[1].GetAddress() + v2Address := blockchain.stateDeliver.Validators.GetValidators()[2].GetAddress() + v3Address := blockchain.stateDeliver.Validators.GetValidators()[3].GetAddress() + + blockchain.validatorsStatuses = map[types.TmAddress]int8{} + blockchain.validatorsStatuses[v1Address] = ValidatorPresent + blockchain.validatorsStatuses[v2Address] = ValidatorPresent + blockchain.validatorsStatuses[v3Address] = ValidatorPresent + + blockchain.stateDeliver.Halts.AddHaltBlock(haltHeight, v1Pubkey) + blockchain.stateDeliver.Halts.AddHaltBlock(haltHeight, v3Pubkey) + if blockchain.isApplicationHalted(haltHeight) { + t.Fatalf("Application halted at height %d", haltHeight) } - return validators, candidates + haltHeight++ + blockchain.stateDeliver.Halts.AddHaltBlock(haltHeight, v1Pubkey) + blockchain.stateDeliver.Halts.AddHaltBlock(haltHeight, v2Pubkey) + if !blockchain.isApplicationHalted(haltHeight) { + t.Fatalf("Application not halted at height %d", haltHeight) + } } diff --git a/core/minter/upgrade3.go b/core/minter/upgrade3.go deleted file mode 100644 index 379428102..000000000 --- a/core/minter/upgrade3.go +++ /dev/null @@ -1,446 +0,0 @@ -package minter - -import ( - eventsdb "github.com/MinterTeam/events-db" - "github.com/MinterTeam/minter-go-node/core/state" - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/helpers" - "github.com/MinterTeam/minter-go-node/upgrades" - "math/big" - "strings" -) - -func ApplyUpgrade3(state *state.State, events eventsdb.IEventsDB) { - list := `720:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mxdd611a177d523bcb3f7faef70dbccc7c78b89e93:10000000000000000000000:FRANKLIN -960:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa69b27ddca35b3438a9f5abfd170cc79d167f99d:50949508056216228178:KARAT -1320:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx96bba42ec0eecf784fe3b3bb4fab0ffeb5e6c560:200000000000000000:LOVECOIN -1800:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx8f16fe070b065b958fa6865bd549193827abc0f8:5000000000000000000000:FUFELL14 -1920:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx065f5324316c467103ad8e8c721b9b7f2b26836c:8612440583210883172:BIP -2040:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mxdb7c5253e849e1116465b616cb3b69154478681f:315000000000000000000:BTT -2280:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxf7ae7dd3c97b2734ffc8e5ad54eadb7e1b1308a9:97696679897506760254:BIP -2760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc128ec9acd82a2a92f483426d06db9de80ee82fa:2350000000000000000000:FRANKLIN -2760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx2daa18881dc75f27d9debacc46fda99a5445d94a:115000000000000000000:FACK10K -2760:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx19db40809286d0d8f287d403024f5a8ae834b304:1000000000000000000000:ROCKNROLL -2760:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx24315150d9877b61d53993e4d007de9f0b8cdec2:1000000000000000000000:BIP -3120:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx3c2e19acf5bab7ca39382cb73cf7fb544ac67236:10075395600411873912:ALPHA -3198:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx3a87af3eb515803ff10bbaa5784ccd66e9b60c58:150000000000000000000:FACK10K -3373:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx8f16fe070b065b958fa6865bd549193827abc0f8:85000000000000000000000:VEXILIFERR -3480:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mxe8381cd918aee5942bd70bafcca0d935da703054:345000000000000000000:MNST -3960:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx2f3b77e109ce73f61f929d0415a42ae4808f6988:7288642318836932269539:VECTRUM -3994:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxcfa7c5d4ef6d5efe025152e8b7cd5307cfab4ffc:266059072503155167552:ALPHA -4223:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx64690ff5480819e37e88456c855ba36682d4e822:79000000000000000000:BIP -4245:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx6baf0b01879662b1979967e1a01d64cd6646a52b:90000000000000000000:BIP -4276:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxd41794f2d58651c44135fc8dc447f6464d24a8bb:90000000000000000000:BIP -4383:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxddf836602a86301d7e00e5b21f7c7bb69a2d7fbf:2000000000000000000000:FRANKLIN -4662:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxf4fcf5a27ff92087129bd69051d81366629edf65:82500000000000000000:KARAT -4662:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxd77163004f5ce2ac3f99677c9a11dc329cb13269:5000000000000000000000:GENESIS -5160:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx30a344ffc322514d809f8da32a7f97c33f18f894:90000000000000000000:BIP -5160:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx876274fda7683706639f96f453699866f763c99c:51830000000000000000:MPRO -5160:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx2daa18881dc75f27d9debacc46fda99a5445d94a:115000000000000000000:FACK10K -5280:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxa0dab5810bf5f667c7fa5ed1433006fba87fbe32:728253397094429504794:BIP -5640:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxbde0012c075024e87fb66ab3ad014b6b81db8c3d:3003574960395749645337:ROCKNROLL -5640:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx57c2ae9be768965f98ab4df0c7fda86042b2b756:223889670732786458:CHAINIK -5760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx541db3945ca1a02562682e1ca8132cd74063a2ce:2000000000000000000000:FRANKLIN -5760:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx64690ff5480819e37e88456c855ba36682d4e822:90000000000000000000:BIP -5880:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx87950dd945b86ffa42198651577e49ae18e1de6f:63000000000000000000:BIP -6000:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx5e66b44e1eec6529752c49bfc8c708310035f32e:1600000000000000000000:BIP -6120:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx43fd592a69fad43c8c882cd280e4b464b2a7583c:88100000000000000000000:AFFILATE -6360:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxf9fc891309605917b4127ac151eb31a9e44637e6:2944000000000000000000:BIP -6360:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx943212f3e5428a29d11c1c6c22739f689b9a79fd:101000000000000000000:AFFILATE -6480:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx3e547ce1dda747b1de1abd5c3c6d48dcfb88b88b:122488046690011534568:BIP -6600:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx5d4396eb0169976da59f7e6e0a95e53b97ae00bb:526820000000000000000:AFFILATE -6960:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx0825e377f259dbf7cd06f5e3a7bf0a642c33edfb:800000000000000000:BIP -6960:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxfafcdfb4f5a4307328f19f6b776db8cd7ee2bcf8:1481645300000000000000:BIP -7080:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxecc5f8214ea59adce6503affd19bda1b16a05770:19321316782411115110:BIP -7200:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxf0b478262f2f931652fe5d3d0e6d71566c891220:10000000000000000000:MRBLACK -7680:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx7f50e4fa2eef1bb3faca1dbfdfe43491d3ef4ee3:73994814771892150786:BIP -7800:Mp3ff4490148c0cf42a9780dbb24a08e012d30d6f371021d28f8e4d3a8e2d6d9cf:Mxb0024e09c92e3c578c7706140ac4582ba7dafa12:3941465748785008068:GAMEZZ -8040:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx4a7ba405fab3c15ceaec716eb9a3ac82adcae9ca:20000000000000000000000:BIP -8160:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mxe8fc28f7558ce5a89119dd76a7f5053fdb4ad242:147800000000000000000:BIP -8280:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxa020f3fead6c0a862bd7bcb899e667eee44fd577:3119861290830091897617:BIRUZA -8400:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx44fdcecd97b88f5b3bccdf2266dbd8e2507bf257:95144868569374506624:BIP -8640:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxb58810523c95a54fb6fda7e0adb09acdffd4cd30:159250200000000000000:BIP -8880:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx0576ef7b02c95b55061c7e49af08b07daea6f23b:17739564141443223755:BIP -9000:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxbdb97854a89798d03583bbaa974e9283ec0c26fe:1000000000000000000:BIP -9120:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx4bb3ff4c53e3078f5268247ae4d24f02285667f3:1095887843938452267791:DESIGN -9120:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxf8d2a6af093385b5ef4b04dc3ba2ab963867caf0:97070960022638037159:TIME -9720:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx3406e85e5dc6cbbfe52a16725969fcf7cf61da58:800000000000000000035:BIPBONUS -9840:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxc7839fdb4a5b4fc2f6d3559f0a458bb1d5fd2f8d:40000000000000000000:GALAXY1 -10080:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx31e8a6caa0a2aeadcb1ad3c4db715af238205476:390000000000000000:PLAKAT -10080:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx21a8d6ac8f592c94f01bfb5b12bdb98cb3575882:1983413301941081789861:CAPITAL -10440:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx7e640862d0f4135143729f76dd7ad071f89249eb:5000000000000000000:PLAKAT -10560:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxb550c550bf538858e95fa594291c85c004601c41:292000000000000000000:CHIP -10680:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx070cd0650784690994e02fee48e57e2eafe17d51:2999845540976787160135:BIRUZA -10680:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxe4f197512e393faed559f40730f0238c5c066666:100000000000000000000:LITTLEBIG -11280:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx59466e1ecb5f22583957377cb8f32a1d9f05ac7a:4735830081657526113:BIP -11760:Mp65758496f8a5d626cac77f5a38894beae0050fdef862da201964cd2fd8111111:Mxb6cf27afb1d43f0c1a54239946eba656ddf7b719:63574911304593469:BIP -12360:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx7e640862d0f4135143729f76dd7ad071f89249eb:20548180455237193660846:STILBON -12360:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx7b62c49a589001d818d695a741fd45feb7677fc2:35763296409768599488:WORLDCOIN -12480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:43111692098939098488:CASH -12480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:26803379835019366643:DOBRO -12480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:258137574566281399720:WORLDCOIN -12480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:2588040721837738868382:LIGMARGAME -12960:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:4013866585422192175:REWARD -14160:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa713cacee41e221416b0e4185d2711c6a637d565:36000000000000000000:ENERGY -15240:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx3c2e19acf5bab7ca39382cb73cf7fb544ac67236:2236402216980526145220:BIP -15480:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbe6b206df4c74bee59e4dd6bd464a7ba51a741bc:1000000000000000000000:FRANKLIN -15840:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx71ca18b482baa95714e556d20255aa12f8d6e8ea:10000000000000000000000:KEEPTHEBIP -16080:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mx845e872ba7400162402df881514b70eb777697ae:3000000000000000000000:BIP -16200:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx7c7c564724e80aaa1d47553c5390008b6f4a89ab:1000000000000000000000:ALPHA -16440:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mxb9d13133cda57fe61fc5c0e66e0ef7b8eb9dddb8:1815907510832552134161:HEALTH -16560:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx5ba0e3efa1ac05a9bbbbb59000b800169040b2c7:932209771174254658822:WILDBIT -16680:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx5f80627a82f79c657045b36cba35abce2dc7a548:500000000000000000000:BIP -16680:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx943212f3e5428a29d11c1c6c22739f689b9a79fd:445420000000000000000:AFFILATE -17040:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx50e9c7195feda1f2a8f62eb7acf8facfb8210a08:20191790000000000000000:BIP -17160:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx6a18282d5579349f10ac901171bddd5e62d652c7:55514943631444791426829:AFFILATE -17160:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxabc09e696e571599ed2649ccd7b90222f7a37b3f:1558413327355746185849:ROCKNROLL -17280:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx14bdb62cea3e57e8e2bae574ecb1c9a9ecb32c4c:2511000000000000000000:AFFILATE -17640:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxcac16cd32911a23127caba14c42065241e97e34f:1000000000000000000:MSCAN -17760:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxd6429ea394ad9e10b4e61f3eb305bc16eeee8239:913792800000000000000:BIP -17880:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxf6f80dcc3551b97753adc2d37de401ae0288f3e4:132000000000000000000:BIP -18000:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx28a36ef48e7538af8f0fe0e528347c0c0db4230d:35000000000000000000:ZERO -18240:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxc7839fdb4a5b4fc2f6d3559f0a458bb1d5fd2f8d:10000000000000000000:GALAXY1 -18240:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx90d178e077b0bacec19556cb9f5b1120670b1197:1228665027914854590162:FRANKLIN -18240:Mp3ff4490148c0cf42a9780dbb24a08e012d30d6f371021d28f8e4d3a8e2d6d9cf:Mxf4fcf5a27ff92087129bd69051d81366629edf65:772428699776254913:BIP -18240:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx845e872ba7400162402df881514b70eb777697ae:3000000000000000000000:BIP -18240:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx7d3a7d965bf8ee95ff7999c8c2af7683cba39be1:22538812514902170853:BIP -18360:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:AMERICAN -18360:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8059198fdd5110b167f9174d8a4fcf5636932aed:20358137596845703:LOBSTER -18360:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:IMAGINARY -18360:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxfda2f0143fbc896c6dac4fa50714661f93187b44:262906496442186187200:SNAPSHOT -18360:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70800000000000000000:BIPBANKER -18360:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:AEROFLOT -18480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:BURGERKING -18480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:GENERAL -18480:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:MARLBORO -18480:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx8a2f6b28e78bcfc43a47d3a454422fb681445b5d:263818192813925416100:FREEDOM -18480:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx940361f744b427749406d9dfc80bf6c00474291a:1055000000000000000000:BIP -18480:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:76000000000000000000:IBANKER -18480:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:BITTEAM -18600:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx6a18282d5579349f10ac901171bddd5e62d652c7:47035711653037566887284:AFFILATE -18600:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mx206d38cf4edcda8919ecb930367020a5c17b7fbc:4999000000000000000000:BIP -18600:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:75000000000000000000:MCDONALDS -18600:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:FREEPORT -18600:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:75000000000000000000:MCDONALD -18600:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:RAIFFEISEN -18600:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx41c8876db5af0e5668bd6461e0edcf9fccc6bdcb:955734957695297206392:BIP -18600:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:GENESISLAB -18600:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:MEADOW -18600:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:KRAFTFOODS -18600:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:BUTTERFLY -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:63000000000000000000:MASTERCARD -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:MIRACLE -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:63000000000000000000:VISACARD -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:75000000000000000000:SILVERCOIN -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:PROFICOIN -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:ROADMAP -18720:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:ROSNEFT -18720:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:62000000000000000000:SBERBANK -18720:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:70000000000000000000:CANDYNODE -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:CAPPUCCINO -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:UMBRELLA -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:GAZPROM -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:COCACOLA -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:DIAMONDS -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:PALADIN -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:WESTERN -18840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:REFLECTION -18960:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxf3ae262a23c30f6aa3be1ddab2a10dfb5e251c15:36000000000000000000:BIP -19080:Mpf2e3975489fbb0c3df8116e7de239d2f5ee72da156983f974a7b6a55f223d3c8:Mx7fd23f9b9e1151e559107aae0b1ac118816bf25b:10294967177126885463234:AFFILATE -19320:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx0784c166c6f33649deeb6d3e030d5d378e4744a0:1851072601610888:BEHEALTHY -19320:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:WALMART -19680:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx8504b25a26939f48a3f8877ff1646b9b5d33129c:100000000000000000000:BIP -20160:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa7795090ded80ae1122d8ece8bdc17cdcaa72a84:215204566204493842877:KARAT -20280:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx7117eb5f6d0b86822dcbad650d848dcce0a12ebd:365295881133658263065:BIP -20280:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxb1a18a1f888752e27bcd7d60064ebb5af673c503:4314680000000000000000:BIP -20400:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx8ec3f0f9ea5e57140d4931aff99c85af85da33e8:1010000000000000000000:BIP -20520:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx96bba42ec0eecf784fe3b3bb4fab0ffeb5e6c560:100000000000000000:LOVECOIN -20520:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx0108df50a1e72b03908d697bebdfadf0516e087a:10000000000000000000:ALPHA -20520:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx063c561965c1363caef0050d9c6b5c517cf6e5d1:3964295060573553972686:EGGCOIN -20640:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxffa76830d38834898ba8ffb61b9a630a431b4981:72000000000000000000:COINPRO -20640:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx92f1475a7c4269ab1b9949609b406fffc67076e9:150000000000000000000:FRANKLIN -20760:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxbdb97854a89798d03583bbaa974e9283ec0c26fe:1000000000000000000:BIP -20760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxb97ecd5fe8f09a00a65801e7d70e61e74f13600c:100000000000000000:100COINS -21000:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxd2194b32f679232fb3bce747bf35750c2e3f18a3:10404119730572297000:BIP -21240:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx4f390f612b7e5f0d6a075720f836e614368d0dbf:7777000000000000000000:FRANKLIN -21240:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx504c113c65f8abadfebf0e72f579fcffdda6f23f:5960192988979158464:BIP -21600:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa4ca13d5831e095aaedc08f172977805e215d34c:137040108084953075701:BIP -21600:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxdd7585be32f4669b92acee3928e59afe832a6afc:2543906195560268140:BIP -21840:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx912dc60742724905ee1434c096464ea82559a5da:960807343727548430024:ELECTRO -22080:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxef57005e8f085f23ee9fae84c3cc467ca7a0e79f:500000000000000000000:EP1CPWR -22080:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc5fed5dbb6c48b99a683ec98540605de14bc078c:284374018093303695804:BIP -22320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx9436047660fbf1c04df3cfae457975733bfc66d0:831636722950215583812738:WILDBIT -22320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx57c2ae9be768965f98ab4df0c7fda86042b2b756:7209808638667525923731:ONLY1 -22320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxd4835b5acf6e87271071dd99d55b8bdd6c651589:1750000000000000000000:PIZZA -22320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx3eac9a8c2063d7b0e140e6349fc7207c3cd2f3d1:1500000000000000000000:PIZZA -22320:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx5340146e93ae3f5e811b0573b7c8780a3f060ced:7246170777851714543:KARAT -22440:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx09606fbeaee7d6d6926c654294690b09e07773f4:2500000000000000000000:PIZZA -22440:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx063c561965c1363caef0050d9c6b5c517cf6e5d1:5000000000000000000:MSCAN -22440:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:SYMANTEC -22560:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxddf836602a86301d7e00e5b21f7c7bb69a2d7fbf:50000000000000000000:FRANKLIN -22560:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx57c2ae9be768965f98ab4df0c7fda86042b2b756:13553602876624029726:KARAT -23040:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx5340146e93ae3f5e811b0573b7c8780a3f060ced:690000000000000000000:KARAT -23040:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa4b8bf84fa6a5a47bf3a7f5a0674ee6b1fe3491f:120000000000000000000:BIP -23160:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbe6b206df4c74bee59e4dd6bd464a7ba51a741bc:490000000000000000000:FRANKLIN -23280:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx8147ec52768d0eb9a101eb3e15b7187f183f6636:1500000000000000000000:PIZZA -23400:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mx1ddf11baaf6e169949a903cb199487a104cdf167:1500000000000000000:INFLOW -23760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx08adb0251013bda4f09effa744ec1cf192717c48:101600015918981066963:BIP -23880:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx062446125fadd8f424891ae16dae0a11b69442d3:278000000000000000000:BIP -24000:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx0c9c587dd552f949c356bbda67c4ad3dd61e2db0:30000000000000000000000:LUCKYLIFE -24000:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxb0b7a8193b02ce8610815ca9138fb168ac0d9f44:377239475575567506534:BIP -24120:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:615643141483183660722:KARAT -24120:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx57c2ae9be768965f98ab4df0c7fda86042b2b756:7879488610183692704:KARAT -24120:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx9822d4fb9d3f212975af1b1fd8324ffd177eab53:54747704448640294147:MPRO -24240:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mx791a6d843077efa0a83b80e84ca598f5fcacf118:10000000000000000000000:GRAMIUM -24240:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx3861de6d792caed96fc84d629a9edd2a54d2ec74:2010028019254643306632:STILBON -24600:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mxd8a267e01afa4624699ee9fc2a2067d59eebdb10:1331000000000000000000:BIP -24600:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:CARNIVAL -24600:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxabc09e696e571599ed2649ccd7b90222f7a37b3f:1500000000000000000000:ROCKNROLL -24600:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx01cb3d112b53e4c0968bf87bbd73a1323ad6b732:500000000000000000000:BIP -24600:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mxe2214434df155bb122c42fe87925eeee5c1788ed:40000000000000000000:BIP -24840:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:1000000000000000000000:GANGSTER -24840:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx7ccfd00ab7689a12aa283eb3d00683cf5348fcec:1068000000000000000000:FRANKLIN -24960:Mp7b4174732a169c467c9ee791576fc5860ca99bc7a49d8cfb041a91f9202178cc:Mxf2202fdf698ad4138dc4d446f72b1c53317ffcd8:400000000000000000:BANANA -24960:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx96bba42ec0eecf784fe3b3bb4fab0ffeb5e6c560:200000000000000000:LOVECOIN -25080:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx88a711cad28d0df19ef4a7f9461dd8855c478a95:3132137331393384066392:BIP -25080:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx27513ee8e47f457b5561e83c25864b65f2f8db0c:1584689887496928605628:QUANTIUM -25200:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:1000000000000000000000:GAMES -25320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx25eb0e6020c0a951200081fd465210f09ab3e56b:10000000000000000000000:VIOLEUM -25320:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxda4819ae2e0edb48f080ed755df902bc1b1c3917:1500000000000000000000000:LIFEMONEY -25320:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mxface92230fc9d9ccf9646924d8a49863d7b9a649:153937522729283569570:TRADERS -25440:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx876274fda7683706639f96f453699866f763c99c:1149871218781843188:BIP -25560:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx69fc61d2ee91d6b50dd34a528bb7171fb4d8f849:500000000000000000000:BIP -25560:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxe9fd1e557a4851fe1ba76def2967da15defa4e4d:39454494455115615902423:BIP -25800:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx24315150d9877b61d53993e4d007de9f0b8cdec2:1200000000000000000000:BIP -26040:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxefaf094072e11f20936c05b8ca5140c45e2e8bf6:1005781048961943237615:AFFILATE -26040:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx66d50e4634b0f62185a2a666bedc9f05e322cce2:162800000000000000000:BIP -26160:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx10306021211dd5190bcd17b9082c4977c457eedd:89763513419195623473:BIPSTORE -26160:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx7ccfd00ab7689a12aa283eb3d00683cf5348fcec:100000000000000000000:FRANKLIN -26280:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxc7a14ba1921de49c37016ce0fea8e4be5bf931de:65766723383130982226:ALPHA -26520:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:217513450893878287618:CAPITAL -26640:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx4d6e737bc043de4cc6f5ffdb0383eb01ccca3457:100000000000000000000:ONLY1 -26640:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:547846135000000000000:KARAT -26760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx30123adc5f91eae372195a1bec0096e545ab1048:1263554828942720300:BIP -26880:Mp3ff4490148c0cf42a9780dbb24a08e012d30d6f371021d28f8e4d3a8e2d6d9cf:Mx227206dd8559b81a292a3dbd529a7193278051b2:86000000000000000000:BIP -27000:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx0c9c587dd552f949c356bbda67c4ad3dd61e2db0:10000000000000000000000:LUCKYLIFE -27360:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:1000000000000000000000:GAMES -27720:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx183c39990c3cf13cf16f89bd5672f2a04f8b4de4:1300000000000000000000:BIP -28080:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx30123adc5f91eae372195a1bec0096e545ab1048:131470000000000000035:BIPBONUS -28200:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx0576ef7b02c95b55061c7e49af08b07daea6f23b:61903195366876440360:BIP -28200:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxd77507ae481352e48615728dad3307d82b6e6895:4502290995554099058424:PIZZA -28200:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxcdbdcf4ce903a9ae2d3b97e8f8b982d1f01cd820:1178652242047212253914179:BIP -28440:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbc0fbb4be660dd846399bd639c88a68ff1613ca0:1018711880563948791473:KARAT -28680:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxb97ecd5fe8f09a00a65801e7d70e61e74f13600c:100000000000000000:100COINS -28920:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx12ae36bee529fa1d78158634380ad2a1654afe79:4000000000000000000000:KARAT -29280:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxb098961f76be4d533509bca197d97b067cdc43e7:12472051126987485441:WILDBIT -29280:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxcebda1b2a6834db97b3d05f386729d2561060960:21993360586919369:BIP -30240:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx9c984bc154f86f2aa37760d8465c416cc139769a:700000000000000000000:BIP -30840:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx04b3719d601e1204666e525367918f152f336ed3:854106370937750564071:WILDBIT -31200:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:10739930341080808280:KARAT -31560:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx10306021211dd5190bcd17b9082c4977c457eedd:455559739904428660949:SOUNDCOIN -32520:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx53fe2bfa554f59addd4abae8c6fd5f6a5bc921e9:7250277603262780739:FRANKLIN -32760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxb32244a93589ceca6d46406b9d74328c771914d9:1890248514428746718521:BIP -33240:Mp65758496f8a5d626cac77f5a38894beae0050fdef862da201964cd2fd8111111:Mx78469e0cf2ac0eb9f4b84b7d27ee8866de402e3e:890659673011110054735:BIP -33480:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx4f6ba53e3f6072e426d2c4da56439064ee756659:99800000000000000000:BIP -33480:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxef90c0384cdc70ccd30713f2c268bf55b2d1ae3b:70000000000000000000:BIP -33840:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:9416146514332110069:KARAT -34080:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxb34b4313aa28e48288cad7ceb3d21204797375ee:1429000000000000000000:BIP -34560:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx1bf6b2e8daba57fc5fb58727f516013bac43b7c2:595605589000000000000:BIP -34800:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxa8b8992366c8d9bc9c329163c1a12714dac0ffee:1100538662221569875034:FUSION -35040:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx35f1b5e75dcaaa25ed15d1b3912d650fea61f823:2683577574444981732:BIP -35160:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxb5c1f6748132bbf7b6baa5e942f392aaece5e283:328351226967225342052:MINTERIUM -35280:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx61992c381a9b065a0c412d67d6ea048bf4d0e699:925197134998364980252:COINOPTIK -35400:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mx9452faa8b6eb2447ebb8e1e1f9e008ed35bcaf89:140000000000000000000:BIP -35520:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxecfe4212667f632ea240e5567cd24cae240054ef:1500000000000000000000:BIP -35520:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx601422af0ec48feb62976c4952a6a4baf5583e90:100700000000000000000:BIP -35760:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx0a58d2d5de600efec190893c7f45ac4387051836:5096679502130353679:BIP -35760:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxffa76830d38834898ba8ffb61b9a630a431b4981:71000000000000000000:ROSTELECOM -36360:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mx711f7a704d12e9e2f9cc928aee2b061522407ec8:25000000000000000000:BIP -36360:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa8ed545a414a1d8fb95b9a3e9d56a46ae46f9c9f:36000830957111522865:KARAT -36480:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxbe5faf1f03fd032b1ea66681ea6f706a2033d8a4:10000000000000000000:UNB200K -36600:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxef57005e8f085f23ee9fae84c3cc467ca7a0e79f:100000000000000000:EP1CPWR -36720:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxc9dcb31dcf9b75ff6b9c7e4fae556147012a3d61:125405174118902032:BIP -36720:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx22c127fdb84e9f4f71a1a1a7be20d6a0ac17cdcd:871298343849:MATRIX -36720:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx2440baf2479851aab6f07f4bd61731b9515c5edf:70000000000000000000:BIP -37200:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxd2da6a788b4fda271abcff2f9fd2de16cd79c527:8001562270222961429:BIP -37560:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx8546f047947fa126f49d005adee1c5809ec60270:1100000000000000000000:BIP -37560:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx33d6a20dc01827e6abb452a66d788fd0d2444537:122000000000000000000:BIP -37680:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbe7dab5a2a7d472a36446541eabb9f0b5b134070:3550730308362633244908:BIP -37680:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx10306021211dd5190bcd17b9082c4977c457eedd:686564852132370232979:SCIENCE -37800:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx936529c5bd5f68c2e9404372f5b2ca9033c8d0d2:3423477563596483015601:KARAT -38040:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx720b19989e78ee78bcdb78b1977cda51a6ad6d07:250000000000000000000:BIP -38400:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx91826fc03077294b332eb71ac3425b604be32ea1:665100073133074720:PLAKAT -38521:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mx039569fdddc1148a83eb251ce731c40c26eb305d:808062273896046504725:BIP -38521:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxc65f0168a26dfbeb6128e2d14fd6e2239c1ec3b1:1000200000000:MINTDROPS -38640:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc38c2b191e1797437ede99e2a7c70d289f81e33c:150000000000000000000:BIP -38760:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx9c213b6970ebf78e1ddff2ac7efa1ee4bbe6acde:2999845838979324071822:BIRUZA -38880:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx6c63799ef4408f4fd93ff5d7e228e3ccfd548df2:181738200000000000000:BIP -38880:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxabf98d720d1df5e3a639648507b7e87fb2204370:2810997878757988652:MATRIX -38880:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx111e470b470d418503a78c4e6bbb0816213c8fa2:1268851996838230149:MATRIX -38880:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx0d516c3cf0fa4cae2c25bc85d756d338be798bc3:1180657293357886440:MATRIX -38880:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxae3772f9ff4954cb2f4fe9796f0478696d36dd4c:187650981834753687554:DESIGN -38880:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc4583af399dbb5d04f1a4dd6cf3f2c4e50aba470:100000000000000000000:BALANCE -38880:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc4583af399dbb5d04f1a4dd6cf3f2c4e50aba470:110000000000000000000:DISBALANCE -38880:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx95b842754ebd588773fa8550e2262d0ad1032c98:115567639629593700373:ALPHA -39360:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx0a58d2d5de600efec190893c7f45ac4387051836:891497838090304335767:BIP -39720:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxbde0012c075024e87fb66ab3ad014b6b81db8c3d:1000000000000000000000:ROCKNROLL -39840:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx44d51edf682330bec6c627abaed107c028e4b5c5:10000000000000000000:BIP -40080:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mx3f82f301b0fdcdaf7d0eb4a45e60a9e7d8be4f39:1000000000000000000000:FRANKLIN -40200:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mx1ba993008a62d7be98b33f92bd85ba1c6a3fdb84:1177692511207742548:MATRIX -40200:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxc57f77767f20954b306c6e098d1a06fe893f65ce:14886686039859121538:MATRIX -40320:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc4583af399dbb5d04f1a4dd6cf3f2c4e50aba470:477000000000000000000:BIOMETRY -40320:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxcaa5a3742b9dc54c5cbd160ed3fa0785798ded13:900000000000000000000:BIP -40680:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx5217595c799f12c9c586b6a7496144a3cbab3446:1000000000000000000000:BIP -40680:Mp7b4174732a169c467c9ee791576fc5860ca99bc7a49d8cfb041a91f9202178cc:Mx66025ad100fbc18c5f51595d6f3effc162d2dcb0:10000000000000000000:CANDY -40920:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx684a6dc651293214a2942d6e5bc5dc6d6b3ec7d2:15341742910846751966:BIP -41040:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx26ad1e4ff99bfd8b72887d9253d88a7922847575:50650000000000000000:MPRO -41160:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxd7cf0bab4624c31fe980ea3faed8cb81b05fa82d:7500000000000000000000:BIP -41160:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx63bfae01a963ae3bd78b03b50de723084a1e2dd8:49770000000000000000:MPRO -41400:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:14495626589077689275:KARAT -41400:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx33d6a20dc01827e6abb452a66d788fd0d2444537:97517549311670217153:BIP -41520:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxe7ab556dcd6474911aa995c0a2d07795ac096d3c:482680462789406289:ALPHA -41880:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxc57f77767f20954b306c6e098d1a06fe893f65ce:3672128237793956594:DOMAIN -41880:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxd568ff3f3b2681bb748b271589e7cdec648c75af:12300000000000000000:BIP -42120:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mx9436047660fbf1c04df3cfae457975733bfc66d0:4094870901300156750:HOSTING -42240:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx9884ade1269f36a84ecfc4d0244c1e93e5a9fe10:4144000000000000000000:BIP -42360:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx6e832344418a9bad70f4ffc95924781993eb33bd:1293497002068770219794:ZERO -42720:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx69fc61d2ee91d6b50dd34a528bb7171fb4d8f849:1348798631654926474563:BIP -42840:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxdd7585be32f4669b92acee3928e59afe832a6afc:7474421589711159660:BIP -43200:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mxf2f738929e1989e6b89d2d2638b5cf9e71332880:67564233678576238125:BIP -43200:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx72730b5c2b659fc57ab2af06461192a23f6354f9:192280765883511575657:KARAT -43320:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx7e17de669bd586e9ef22d515eb68675edbdf1b48:6000000000000000000:MNST -43320:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx183c6d4e55f4a0b1c0ae22f99ee1c0e0211dfce0:21000000000000000000:ROBINCOIN -43320:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx183c6d4e55f4a0b1c0ae22f99ee1c0e0211dfce0:200000000000000000000:GANGSTER -43440:Mp999d3789d40ff0c699f861758bcafde15d3b4828c7518bc94810837688888888:Mx1293bf099d359dffedf6f317fe9742179ce42241:1000000000000000000000:EGGCOIN -43440:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbe6b206df4c74bee59e4dd6bd464a7ba51a741bc:1000000000000000000:BIP -43800:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx6b32d1a0cff21de94fc6c519eaf85f264f016431:3000000000000000000:BTT -44040:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx566367694688785876927b5d57e25b6487b6f679:1000000000000000000:PRIVILEGE -44160:Mp7779ec1c6492e7c71a36f4009d7ee5a43fed2fe4048882b8a099e748869f0777:Mx36c6db1a37e9fe1b51df5dedcd60d8339c696111:465698355349903514000:WORLDCOIN -44400:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mxe4f197512e393faed559f40730f0238c5c066666:100000000000000000000:LITTLEBIG -44640:Mp7779ec1c6492e7c71a36f4009d7ee5a43fed2fe4048882b8a099e748869f0777:Mx84bef90cfda16a45d7a357e5c6e4833d99cfc741:1001100000000000000:4DREAM -45120:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx438c163efa201012690295a8ab4cdc0ae75edd7b:1190503776321856753636:MINTIUM -45720:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxcebda1b2a6834db97b3d05f386729d2561060960:2091749961963592741:BIP -45960:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxbde0012c075024e87fb66ab3ad014b6b81db8c3d:102583546473431954520:KARAT -47640:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxbd2c0c8cc16931b0fbe08dd173fd7c953dcaad58:1000000000000000000:IAMIMPROV -47640:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxf559a0609425bb8a41c5a275d6b7e65607de9321:13013100000000000000:LOVECOIN -48240:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:11797753320201586028:KARAT -48600:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mxef57005e8f085f23ee9fae84c3cc467ca7a0e79f:500100000000000000000:EP1CPWR -48600:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mxc0be46f30934181370ee02b716935805ef073ee7:100000000000000000000:BIP -49800:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx72effc233e0957d12288635f54c0fcada4cd1184:60402513926457125311:LASHIN -50280:Mp999d3789d40ff0c699f861758bcafde15d3b4828c7518bc94810837688888888:Mx4b966dce44f9a7c1786ab9ae92c5a94626d76842:110516998724015148:ENLIGHTEN -51000:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx8031f68b35b0d77139b96ff7afd1ef11e3e41e25:4000000000000000000:BIP -51120:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mx6cef43e6a00a1fbe6b162b946dd13245b4efe2e1:50000000000000000000:WHIPPING -51120:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxdf8a3187318cc77688a844ad6956e20d04cf2732:3029893000000000000000:BIP -51480:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx655a9fb0543d41121ae2855bdf673e5e9e688d04:90000000000000000000:BIP -51600:Mp3ff4490148c0cf42a9780dbb24a08e012d30d6f371021d28f8e4d3a8e2d6d9cf:Mx6d93344a3bd5483e927bb324e38f7d76e8c4c33e:1441593984980926645516:FRANKLIN -51720:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mx5a21fb54e94ab855c9092cc25955cdccb296044c:52241668071302600354:BIPMEX -51720:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx8036617bf70954c9139f7156866c9975e4ccbb08:2000000000000000000:FREEDOM -52080:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx9d0d1c894c06bd96718558d5a22ce0ecbc7e3830:283544800000000000000:TRACKCOIN -52080:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx99ddb4f50c3183718e4af1f255435270a8cd3605:7719401443998266633:MPRO -52200:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx06acaf371bf50fd4cf82c2952bf8f5dbb131a449:1010000000000000000000:BIP -52320:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx41f93b51f87b1c4b4d4230b5f2d3dd1188d3fa5e:101000000000000000000:BIP -52440:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxc37ee66efbf36025d6b481e661b55d3a52ef817e:101847756969931054261:BIP -52440:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxf8ff98415480e1972fddc80ddf8e3bd2be5ecbed:103799165465886561765:BIP -52680:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxf6b4e7245ab3ce3d76bf522490322aa270653b84:5148709312628042447928:BIP -52680:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx51ffc60c86c29e2250f30e2b19169a526b65edaf:1000000000000000000:4DREAM -52680:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxd79b89e6f0e8ef45ffc8da70810e7fb74b68b6d3:1000000000000000000:4DREAM -52680:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx41f93b51f87b1c4b4d4230b5f2d3dd1188d3fa5e:1001000000000000000000:BIP -52920:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx7d47a4a24588088f1af565e4e383877733a335d9:1000000000000000000:4DREAM -52920:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx8036617bf70954c9139f7156866c9975e4ccbb08:4000000000000000000000:FREEDOM -53040:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxd1399b428b1f6f855bebe2bf2a57f77ccf233df2:114785367435876917650:BIP -53520:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx0a58d2d5de600efec190893c7f45ac4387051836:5267062284092328550:BIP -53520:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxf559a0609425bb8a41c5a275d6b7e65607de9321:390161883257509598932:WILDBIT -53520:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx57b20f1004438a2539c851d02704081944d76016:75000000000000000000:TRADERS -53520:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx8383a7c2057dc71c2f7328f3c0bfc6fc82533c6c:20000000000000000000:BIP -53640:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx4bc5be896635ac43c8b7cb61f2772cff4d606b17:37071089542645362610:MPRO -53880:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx705ede3cf7d49f664b31db5c0ce795d8afd0addf:1127526700871423569720:FRANKLIN -53880:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxe7ab556dcd6474911aa995c0a2d07795ac096d3c:254999854671847786623911:ALPHA -54120:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx8036617bf70954c9139f7156866c9975e4ccbb08:2000953169641907344853:FREEDOM -54360:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx047f300ee314b9b3498657b8d73ac85aa7144032:1000000000000000000000:BIP -54360:Mp5e3e1da62c7eabd9d8d168a36a92727fc1970a54ec61eadd285d4199c41191d7:Mxfda2f0143fbc896c6dac4fa50714661f93187b44:1420729236340788392766:FREEDOM -54480:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx047f300ee314b9b3498657b8d73ac85aa7144032:640000000000000000000:BIP -54600:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxc2836035e7d25658cfa229646d77f4a1a07e6cca:16239040671383714957:KARAT -54817:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx57b20f1004438a2539c851d02704081944d76016:419729584279382637:BIPNET -54817:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxd7e9ed48a659c0cd0f7faf48e5502057ccdd6942:50000000000000000000:BIP -54943:Mp7779ec1c6492e7c71a36f4009d7ee5a43fed2fe4048882b8a099e748869f0777:Mx161522eacb8ace354d3dbce39d7ed60b7d601477:70329055830012847302:BIP -55080:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx49ee05172a871d69ed20540015103fa5fa901917:80000000000000000000:ROCKNROLL -55080:Mp92d05e745464f4d17a12440f48d73dddf8135e45cf91a7e3c4bcacdb2cc1c99f:Mxbde0012c075024e87fb66ab3ad014b6b81db8c3d:31000000000000000000:BIZXBOX -55200:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mxab0659c1f6b8b535712376278ef11b62397669de:6768841063763402356493:FREEDOM -55320:Mpa70d12c096cb459e7c191be0dffb1a7dbe4a9587bfa7174c654959e5e9da7203:Mxf533360a79da177f834a9667596a523402beae8b:3000000000000000000:CASHXCASH -55320:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mxb098961f76be4d533509bca197d97b067cdc43e7:55705920774912805403:WILDBIT -55680:Mpeee9614b63a7ed6370ccd1fa227222fa30d6106770145c55bd4b482b88888888:Mx629e48e818797596f3e75929eccb7bdbd25e892e:10287900000000000000:BIP -55680:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx63b524c1136ce92e235e2ad1ac32f39949fae345:334800859985912571117368:BIGCOIN -55800:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mxee3c4bf8265b8cbedb361fc46b209aa8a0fd997e:10000000000000000000:BIP -55920:Mpeee9614b63a7ed6370ccd1fa227222fa30d6106770145c55bd4b482b88888888:Mxd09a11b45aa8f52813a14a56f841e466f0e44266:66270553501143815:POPE -56160:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx51926e5d89e582433881c9bda6d82df0dc7bb166:3010000000000000000000:FRANKLIN -56280:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx8504b25a26939f48a3f8877ff1646b9b5d33129c:100000000000000000000:BIP -56280:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx381588dddb6736e1596b76da5d1e86e9fc5bf3bb:59800000000000000000:BIP -56280:Mpeee9614b63a7ed6370ccd1fa227222fa30d6106770145c55bd4b482b88888888:Mx629e48e818797596f3e75929eccb7bdbd25e892e:10000000000000000000:BIP -56400:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx938d9bf3857843d976c2f2e1b11a3179aa4b69e8:1000000000000000000000:BIP -56400:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx89b2c0f1d6b056aa5e069b92867d414083484e66:102503403763750610145:ALPHA -56400:Mp02bc3c3f77d5ab9732ef9fc3801a6d72dc18f88328c14dc735648abfe551f50f:Mx39cdacf9776c00f13cf10a80e85f1cd967ea08c2:101224672885746066656:BIP -56760:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx55cb82d7085da20abc2bd6cf26447b40521014c0:1000000000000000000:4DREAM -56880:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa69b27ddca35b3438a9f5abfd170cc79d167f99d:10333522528525841120:KARAT -57000:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx05362cfc04795b81ca94d6fa1ad3919fa26b5d79:2091248449997812476909:NUT -57000:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx00fc7bf5dfb882794bbc36b44fb0bdfdfd29aed7:1500000000000000000000000:ETERNITY -57360:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx381588dddb6736e1596b76da5d1e86e9fc5bf3bb:545000000000000000000:BIP -57720:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mxf8a9843e73585548d982d2858d3697ca0c6158b5:399800000000000000000:BIP -57720:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx87cb13fbb3607282e91ab27c01a436c9a9390535:44355983558951213109:FRANKLIN -57720:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx87cb13fbb3607282e91ab27c01a436c9a9390535:15162853093488807771:KARAT -57960:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mx49ee05172a871d69ed20540015103fa5fa901917:600000000000000000000:BIP -58200:Mp2cbade2b08501047bb9d2a08abeee21e276cb0e027e84e3a526ede85c4cbd442:Mxf277a5dd1fc8dc70b1aef6b81e8fcdabee463494:10700000000000000000:LOVECOIN -58200:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mx938d9bf3857843d976c2f2e1b11a3179aa4b69e8:200000000000000000000:BIP -58320:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx11a013438124097b2baf17bcd8737c8c177ef8de:1000000000000000000:BIP -58440:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx938d9bf3857843d976c2f2e1b11a3179aa4b69e8:290000000000000000000:BIP -58560:Mpeee9614b63a7ed6370ccd1fa227222fa30d6106770145c55bd4b482b88888888:Mxa50ae74607930530430c61ca246120fefeab8d0c:1300000000000000000000:BIP -58680:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx6cc4c2b7e2aab57350c697f48048b3a0a6a6055d:600000000000000000000:BIP -58680:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mxf9fc891309605917b4127ac151eb31a9e44637e6:2008000000000000000000:ALPHA -58680:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxb0bdfa9aa7512a87da4dcbc44b2391cca5f89e8b:734541154582255964606:BIP -58920:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxb9b20e085a581d42f74101399b8d65c7753643dc:60000000000000000000:MPRO -59280:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxb8229efbccbfede7d668b48e5f30f0744e961408:480000000000000000000:BIP -59400:Mp120c15e48aed0ac866a1a918bd367cfa31909a6b09f328a18bd18f32edef2be8:Mxfc59af550e6360a90d71ab7b55f50f2f8cfdaa89:350000000000000000000:GENESIS -59520:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa4ca13d5831e095aaedc08f172977805e215d34c:235754514054095950301:BIP -59520:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx065f5324316c467103ad8e8c721b9b7f2b26836c:19739348224325625487:BIP -59640:Mp2c827f0365d6592b5377b771d36cb6b1006ca13243122476800febab59b4a2e9:Mx81855472acef3c71d30b880830b350f36742dec8:87591097527093558566:ALPHA -60120:Mp7779ec1c6492e7c71a36f4009d7ee5a43fed2fe4048882b8a099e748869f0777:Mxe6e7502505db2fd77d827a3466ad4341396a99d2:1000000000000000000000:ROCKNROLL -60240:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx87cb13fbb3607282e91ab27c01a436c9a9390535:56218416172736415259:TRADERS -60600:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mx1ddf11baaf6e169949a903cb199487a104cdf167:20000000000000000000:2RICH -60600:Mp00454817134295dc6d39517672b2467ee8301a836216ace04bf0cf3910000000:Mx3d0f5efad5ee44bbf9a9327c67b5f85f85ab6e69:1000000000000000000:LOVECOIN -60600:Mp00454817134295dc6d39517672b2467ee8301a836216ace04bf0cf3910000000:Mx390f3d312f1b5999217ec5fddd1ec32d0188dbbc:20045000000000000000000:WILDBIT -60840:Mpfe176f944623a8ca9f409a62f0ea3ca75c1cf8d89970adf9384fc9ae8d77fa0b:Mx03f0a3dfc3f933f01ad73011a04ec782f04c1088:31000800000000000000000:BIP -60840:Mpc6df0dd44cae3cb49ee0ef8bd78750b961e64e231e6dfdc22fbd8e0c476d66d2:Mx183c6d4e55f4a0b1c0ae22f99ee1c0e0211dfce0:10000000000000000000:ROCKNROLL -60840:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxa3cee87f82b9c796e902caf2ac9410e909b072a9:212153044468361763535:SAFECOIN -60960:Mpeee9614b63a7ed6370ccd1fa227222fa30d6106770145c55bd4b482b88888888:Mxdd587dad689a72b1be371932f52b6dc8cbfc584a:3254768990000000000000000:EMPIRE -60960:Mp00454817134295dc6d39517672b2467ee8301a836216ace04bf0cf3910000000:Mx2bd36881a63611e07cdeb073fb601e7021eb668b:24286522037138846422704:WILDBIT -61200:Mp4926c68cec9b85d743810c801c35c33bd3d1e74ae0a801e4e08998e656835727:Mx22c127fdb84e9f4f71a1a1a7be20d6a0ac17cdcd:1004158037577:MATRIX -61320:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxbee8d34f238e3561cd52710f726ef575834cbebe:10625060778296898000:BIP -61560:Mp02ff680cbea3fb95f547bddde69c9150b3b7ab8d1c5c2a1bf94ccb70bf073b2c:Mxe2214434df155bb122c42fe87925eeee5c1788ed:14770000000000000000:BIP -61680:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx916ba692019f1bec85aa1b19be91f12e2b7b66f4:50000000000000000000000:KRFCOIN -61800:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx13f2b88bf48e6ff16af3718069a31b0f7bdd604a:400693287485955378433:PIZZA -61800:Mp88883bcd4e9a4eeb7f5a7b7d1f4c02ac0fadc268824694fcccc84e39f4e08888:Mx6abef0c4e2b50f2438971e36fc8a27a4e5ff10af:2000000000000000000:LOVECOIN -61920:Mp03478aae43a1a660573fab0763ae44492cdaf8deffc3fcbcc844acd67dfb2db6:Mxc4e4e5ca42b20e25fa167f0229197806c9d76fa3:800000000000000000000:BIP -62040:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mx832fe661435e61aa7b00e6ee5481d9dfa459406a:138356434058965219962:ZENNOROBOT -62160:Mp1ada5ac409b965623bf6a4320260190038ae27230abfb5ebc9158280cdffffff:Mx94fa414f99831784b29f7763dbde610b2f60e5ef:299960185501040069045258:BIP -63960:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxb550c550bf538858e95fa594291c85c004601c41:570000000000000000000:BIP -69840:Mp4881ad167ca5fb5886322841f992d68aed894ffcb58abc080e8ad3b156f1045b:Mxf5755f152ca461796ea64d311bfeac9c1113433b:1110772217949376359725:BIP -76920:Mpd7a9eae76d3e98145b6b897c00c407b42ae8c42f937527ebdcd0239a4d88c0a5:Mxb4d201fb7a03686f13277c566db323aea6aa0631:10426743932470850000:BIP -88560:Mp00454817134295dc6d39517672b2467ee8301a836216ace04bf0cf3910000000:Mx8017588598dad6323c1d4c7814aa99bc972b20c3:8452723075253212652730:WILDBIT` - - lines := strings.Split(list, "\n") - for _, line := range lines { - data := strings.Split(line, ":") - pubkey := types.HexToPubkey(data[1]) - owner := types.HexToAddress(data[2]) - value := helpers.StringToBigInt(data[3]) - coin := types.StrToCoinSymbol(data[4]) - - events.AddEvent(upgrades.UpgradeBlock3, eventsdb.UnbondEvent{ - Address: owner, - Amount: value.String(), - Coin: coin, - ValidatorPubKey: pubkey, - }) - state.Accounts.AddBalance(owner, coin, value) - state.Checker.AddCoin(coin, big.NewInt(0).Neg(value)) - } -} diff --git a/core/minter/upgrade3_test.go b/core/minter/upgrade3_test.go deleted file mode 100644 index 8574ac6bb..000000000 --- a/core/minter/upgrade3_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package minter - -import ( - eventsdb "github.com/MinterTeam/events-db" - "github.com/MinterTeam/minter-go-node/core/state" - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/helpers" - db "github.com/tendermint/tm-db" - "testing" -) - -func TestApplyUpgrade3(t *testing.T) { - cState := getState() - - ApplyUpgrade3(cState, emptyEvents{}) - - address := types.HexToAddress("Mx8f16fe070b065b958fa6865bd549193827abc0f8") - - { - targetBalance := helpers.StringToBigInt("5000000000000000000000") - balance := cState.Accounts.GetBalance(address, types.StrToCoinSymbol("FUFELL14")) - if balance.Cmp(targetBalance) != 0 { - t.Fatalf("Balance of %s is not correct", address) - } - } - - { - targetBalance := helpers.StringToBigInt("85000000000000000000000") - balance := cState.Accounts.GetBalance(address, types.StrToCoinSymbol("VEXILIFERR")) - if balance.Cmp(targetBalance) != 0 { - t.Fatalf("Balance of %s is not correct", address) - } - } - - if err := cState.Check(); err != nil { - t.Fatal(err) - } -} - -func getState() *state.State { - s, err := state.NewState(0, db.NewMemDB(), emptyEvents{}, 1, 1) - - if err != nil { - panic(err) - } - - return s -} - -type emptyEvents struct{} - -func (e emptyEvents) AddEvent(height uint32, event eventsdb.Event) {} -func (e emptyEvents) LoadEvents(height uint32) eventsdb.Events { return eventsdb.Events{} } -func (e emptyEvents) CommitEvents() error { return nil } diff --git a/core/rewards/rewards.go b/core/rewards/rewards.go index 4773a3698..623e58e12 100644 --- a/core/rewards/rewards.go +++ b/core/rewards/rewards.go @@ -1,9 +1,10 @@ package rewards import ( + "math/big" + "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/helpers" - "math/big" ) const lastBlock = 43702611 @@ -11,8 +12,9 @@ const firstReward = 333 const lastReward = 68 var startHeight uint64 = 0 -var BeforeGenesis = big.NewInt(0) +var beforeGenesis = big.NewInt(0) +// GetRewardForBlock returns reward for creation of given block. If there is no reward - returns 0. func GetRewardForBlock(blockHeight uint64) *big.Int { blockHeight += startHeight @@ -34,9 +36,10 @@ func GetRewardForBlock(blockHeight uint64) *big.Int { return helpers.BipToPip(reward) } +// SetStartHeight sets base height for rewards calculations func SetStartHeight(sHeight uint64) { for i := uint64(1); i <= sHeight; i++ { - BeforeGenesis.Add(BeforeGenesis, GetRewardForBlock(i)) + beforeGenesis.Add(beforeGenesis, GetRewardForBlock(i)) } startHeight = sHeight diff --git a/core/state/accounts/accounts.go b/core/state/accounts/accounts.go index 57f282257..405743805 100644 --- a/core/state/accounts/accounts.go +++ b/core/state/accounts/accounts.go @@ -5,10 +5,9 @@ import ( "fmt" "github.com/MinterTeam/minter-go-node/core/state/bus" "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" - "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" "sort" "sync" @@ -18,17 +17,31 @@ const mainPrefix = byte('a') const coinsPrefix = byte('c') const balancePrefix = byte('b') +type RAccounts interface { + Export(state *types.AppState) + GetAccount(address types.Address) *Model + GetNonce(address types.Address) uint64 + GetBalance(address types.Address, coin types.CoinID) *big.Int + GetBalances(address types.Address) []Balance + ExistsMultisig(msigAddress types.Address) bool +} + type Accounts struct { list map[types.Address]*Model dirty map[types.Address]struct{} - iavl tree.Tree + iavl tree.MTree bus *bus.Bus lock sync.RWMutex } -func NewAccounts(stateBus *bus.Bus, iavl tree.Tree) (*Accounts, error) { +type Balance struct { + Coin bus.Coin + Value *big.Int +} + +func NewAccounts(stateBus *bus.Bus, iavl tree.MTree) (*Accounts, error) { accounts := &Accounts{iavl: iavl, bus: stateBus, list: map[types.Address]*Model{}, dirty: map[types.Address]struct{}{}} accounts.bus.SetAccounts(NewBus(accounts)) @@ -82,7 +95,7 @@ func (a *Accounts) Commit() error { path := []byte{mainPrefix} path = append(path, address[:]...) path = append(path, balancePrefix) - path = append(path, coin[:]...) + path = append(path, coin.Bytes()...) balance := account.getBalance(coin) if balance.Cmp(big.NewInt(0)) == 0 { @@ -92,7 +105,7 @@ func (a *Accounts) Commit() error { } } - account.dirtyBalances = map[types.CoinSymbol]struct{}{} + account.dirtyBalances = map[types.CoinID]struct{}{} } } @@ -112,12 +125,12 @@ func (a *Accounts) getOrderedDirtyAccounts() []types.Address { return keys } -func (a *Accounts) AddBalance(address types.Address, coin types.CoinSymbol, amount *big.Int) { +func (a *Accounts) AddBalance(address types.Address, coin types.CoinID, amount *big.Int) { balance := a.GetBalance(address, coin) a.SetBalance(address, coin, big.NewInt(0).Add(balance, amount)) } -func (a *Accounts) GetBalance(address types.Address, coin types.CoinSymbol) *big.Int { +func (a *Accounts) GetBalance(address types.Address, coin types.CoinID) *big.Int { account := a.getOrNew(address) if !account.hasCoin(coin) { return big.NewInt(0) @@ -129,7 +142,7 @@ func (a *Accounts) GetBalance(address types.Address, coin types.CoinSymbol) *big path := []byte{mainPrefix} path = append(path, address[:]...) path = append(path, balancePrefix) - path = append(path, coin[:]...) + path = append(path, coin.Bytes()...) _, enc := a.iavl.Get(path) if len(enc) != 0 { @@ -142,12 +155,12 @@ func (a *Accounts) GetBalance(address types.Address, coin types.CoinSymbol) *big return big.NewInt(0).Set(account.balances[coin]) } -func (a *Accounts) SubBalance(address types.Address, coin types.CoinSymbol, amount *big.Int) { +func (a *Accounts) SubBalance(address types.Address, coin types.CoinID, amount *big.Int) { balance := big.NewInt(0).Sub(a.GetBalance(address, coin), amount) a.SetBalance(address, coin, balance) } -func (a *Accounts) SetBalance(address types.Address, coin types.CoinSymbol, amount *big.Int) { +func (a *Accounts) SetBalance(address types.Address, coin types.CoinID, amount *big.Int) { account := a.getOrNew(address) oldBalance := a.GetBalance(address, coin) a.bus.Checker().AddCoin(coin, big.NewInt(0).Sub(amount, oldBalance)) @@ -177,35 +190,44 @@ func (a *Accounts) ExistsMultisig(msigAddress types.Address) bool { return false } -func (a *Accounts) CreateMultisig(weights []uint, addresses []types.Address, threshold uint, height uint64) types.Address { - msig := Multisig{ - Weights: weights, - Threshold: threshold, - Addresses: addresses, - } - address := msig.Address() - +func (a *Accounts) CreateMultisig(weights []uint32, addresses []types.Address, threshold uint32, address types.Address) types.Address { account := a.get(address) - if account == nil { account = &Model{ Nonce: 0, - MultisigData: msig, address: address, - coins: []types.CoinSymbol{}, - balances: map[types.CoinSymbol]*big.Int{}, + coins: []types.CoinID{}, + balances: map[types.CoinID]*big.Int{}, markDirty: a.markDirty, - dirtyBalances: map[types.CoinSymbol]struct{}{}, + isNew: true, + dirtyBalances: map[types.CoinID]struct{}{}, } } - account.MultisigData = msig + account.MultisigData = Multisig{ + Weights: weights, + Threshold: threshold, + Addresses: addresses, + } account.markDirty(account.address) + account.isDirty = true + a.setToMap(address, account) - if height > upgrades.UpgradeBlock1 { - account.isDirty = true + return address +} + +func (a *Accounts) EditMultisig(threshold uint32, weights []uint32, addresses []types.Address, address types.Address) types.Address { + account := a.get(address) + + msig := Multisig{ + Threshold: threshold, + Weights: weights, + Addresses: addresses, } + account.MultisigData = msig + account.markDirty(account.address) + account.isDirty = true a.setToMap(address, account) return address @@ -229,9 +251,9 @@ func (a *Accounts) get(address types.Address) *Model { } account.address = address - account.balances = map[types.CoinSymbol]*big.Int{} + account.balances = map[types.CoinID]*big.Int{} account.markDirty = a.markDirty - account.dirtyBalances = map[types.CoinSymbol]struct{}{} + account.dirtyBalances = map[types.CoinID]struct{}{} // load coins path = []byte{mainPrefix} @@ -239,7 +261,7 @@ func (a *Accounts) get(address types.Address) *Model { path = append(path, coinsPrefix) _, enc = a.iavl.Get(path) if len(enc) != 0 { - var coins []types.CoinSymbol + var coins []types.CoinID if err := rlp.DecodeBytes(enc, &coins); err != nil { panic(fmt.Sprintf("failed to decode coins list at address %s: %s", address.String(), err)) } @@ -257,10 +279,10 @@ func (a *Accounts) getOrNew(address types.Address) *Model { account = &Model{ Nonce: 0, address: address, - coins: []types.CoinSymbol{}, - balances: map[types.CoinSymbol]*big.Int{}, + coins: []types.CoinID{}, + balances: map[types.CoinID]*big.Int{}, markDirty: a.markDirty, - dirtyBalances: map[types.CoinSymbol]struct{}{}, + dirtyBalances: map[types.CoinID]struct{}{}, isNew: true, } a.setToMap(address, account) @@ -275,27 +297,20 @@ func (a *Accounts) GetNonce(address types.Address) uint64 { return account.Nonce } -func (a *Accounts) GetBalances(address types.Address) map[types.CoinSymbol]*big.Int { +func (a *Accounts) GetBalances(address types.Address) []Balance { account := a.getOrNew(address) - balances := make(map[types.CoinSymbol]*big.Int, len(account.coins)) - for _, coin := range account.coins { - balances[coin] = a.GetBalance(address, coin) + balances := make([]Balance, len(account.coins)) + for key, id := range account.coins { + balances[key] = Balance{ + Coin: *a.bus.Coins().GetCoin(id), + Value: a.GetBalance(address, id), + } } return balances } -func (a *Accounts) DeleteCoin(address types.Address, symbol types.CoinSymbol) { - balance := a.GetBalance(address, symbol) - coin := a.bus.Coins().GetCoin(symbol) - - ret := formula.CalculateSaleReturn(coin.Volume, coin.Reserve, 100, balance) - - a.AddBalance(address, types.GetBaseCoin(), ret) - a.SetBalance(address, symbol, big.NewInt(0)) -} - func (a *Accounts) markDirty(addr types.Address) { a.dirty[addr] = struct{}{} } @@ -313,16 +328,16 @@ func (a *Accounts) Export(state *types.AppState) { account := a.get(address) var balance []types.Balance - for coin, value := range a.GetBalances(account.address) { + for _, b := range a.GetBalances(account.address) { balance = append(balance, types.Balance{ - Coin: coin, - Value: value.String(), + Coin: uint64(b.Coin.ID), + Value: b.Value.String(), }) } // sort balances by coin symbol sort.SliceStable(balance, func(i, j int) bool { - return bytes.Compare(balance[i].Coin.Bytes(), balance[j].Coin.Bytes()) == 1 + return bytes.Compare(types.CoinID(balance[i].Coin).Bytes(), types.CoinID(balance[j].Coin).Bytes()) == 1 }) acc := types.Account{ @@ -332,9 +347,13 @@ func (a *Accounts) Export(state *types.AppState) { } if account.IsMultisig() { + var weights []uint64 + for _, weight := range account.MultisigData.Weights { + weights = append(weights, uint64(weight)) + } acc.MultisigData = &types.Multisig{ - Weights: account.MultisigData.Weights, - Threshold: account.MultisigData.Threshold, + Weights: weights, + Threshold: uint64(account.MultisigData.Threshold), Addresses: account.MultisigData.Addresses, } } diff --git a/core/state/accounts/accounts_test.go b/core/state/accounts/accounts_test.go new file mode 100644 index 000000000..f7a591208 --- /dev/null +++ b/core/state/accounts/accounts_test.go @@ -0,0 +1,382 @@ +package accounts + +import ( + "encoding/json" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/checker" + "github.com/MinterTeam/minter-go-node/core/state/coins" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/tree" + db "github.com/tendermint/tm-db" + "math/big" + "testing" +) + +func TestAccounts_CreateMultisig(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + multisigAddr := accounts.CreateMultisig([]uint32{1, 1, 2}, []types.Address{[20]byte{1}, [20]byte{2}, [20]byte{3}}, 2, [20]byte{4}) + + account := accounts.GetAccount(multisigAddr) + if account == nil { + t.Fatal("account is nil") + } + + if !account.IsMultisig() { + t.Fatal("account is not multisig") + } + + multisig := account.Multisig() + if multisig.GetWeight([20]byte{1, 1, 2, 3, 4, 5}) != 0 { + t.Fatal("address weight not equal 0") + } + if multisig.GetWeight([20]byte{1}) != 1 { + t.Fatal("address weight not equal 1") + } + if multisig.GetWeight([20]byte{2}) != 1 { + t.Fatal("address weight not equal 1") + } + if multisig.GetWeight([20]byte{3}) != 2 { + t.Fatal("address weight not equal 2") + } + if multisig.Threshold != 2 { + t.Fatal("threshold not equal 2") + } +} + +func TestAccounts_SetNonce(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetNonce([20]byte{4}, 5) + if accounts.GetNonce([20]byte{4}) != 5 { + t.Fatal("nonce not equal 5") + } +} + +func TestAccounts_SetBalance(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + account := accounts.GetAccount([20]byte{4}) + if account == nil { + t.Fatal("account is nil") + } + if account.getBalance(0).String() != "1000" { + t.Fatal("balance of coin ID '0' not equal 1000") + } +} + +func TestAccounts_SetBalance_fromDB(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + err = accounts.Commit() + if err != nil { + t.Fatal(err) + } + accounts, err = NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + if accounts.GetBalance([20]byte{4}, 0).String() != "1000" { + t.Fatal("balance of coin ID '0' not equal 1000") + } +} + +func TestAccounts_SetBalance_0(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(100)) + accounts.SetBalance([20]byte{4}, 0, big.NewInt(0)) + accounts.SetBalance([20]byte{4}, 1, big.NewInt(0)) + account := accounts.GetAccount([20]byte{4}) + if account == nil { + t.Fatal("account is nil") + } + if accounts.GetBalance([20]byte{4}, 0).String() != "0" { + t.Fatal("balance of coin ID '0' is not 0") + } + if accounts.GetBalance([20]byte{4}, 1).String() != "0" { + t.Fatal("balance of coin ID '1' is not 0") + } +} + +func TestAccounts_GetBalances(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + busCoins, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetCoins(coins.NewBus(busCoins)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + coinsState.Create(1, + types.StrToCoinSymbol("AAA"), + "AAACOIN", + helpers.BipToPip(big.NewInt(10)), + 10, + helpers.BipToPip(big.NewInt(10000)), + big.NewInt(0).Exp(big.NewInt(10), big.NewInt(10+18), nil), + nil) + + err = coinsState.Commit() + if err != nil { + t.Fatal(err) + } + + symbol := coinsState.GetCoinBySymbol(types.StrToCoinSymbol("AAA"), 0) + if symbol == nil { + t.Fatal("coin not found") + } + + accounts.SetBalance([20]byte{4}, symbol.ID(), big.NewInt(1001)) + + balances := accounts.GetBalances([20]byte{4}) + if len(balances) != 2 { + t.Fatal("count of coin on balance not equal 2") + } + if balances[0].Value.String() != "1000" { + t.Fatal("balance of coin ID '0' not equal 1000") + } + if balances[1].Value.String() != "1001" { + t.Log(balances[1].Value.String()) + t.Fatal("balance of coin 'AAA' not equal 1001") + } +} + +func TestAccounts_ExistsMultisig(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + msigAddress := CreateMultisigAddress([20]byte{4}, 12) + if accounts.ExistsMultisig(msigAddress) { + t.Fatal("multisig address is busy") + } + + accounts.SetBalance(msigAddress, 0, big.NewInt(1)) + if accounts.ExistsMultisig(msigAddress) { + t.Fatal("multisig address is busy") + } + + accounts.SetNonce(msigAddress, 1) + if !accounts.ExistsMultisig(msigAddress) { + t.Fatal("multisig address is not busy") + } + + accounts.SetNonce(msigAddress, 0) + + _ = accounts.CreateMultisig([]uint32{1, 1, 2}, []types.Address{[20]byte{1}, [20]byte{2}, [20]byte{3}}, 2, msigAddress) + + if !accounts.ExistsMultisig(msigAddress) { + t.Fatal("multisig address is free") + } +} + +func TestAccounts_AddBalance_bus(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + + accounts.bus.Accounts().AddBalance([20]byte{4}, 0, big.NewInt(1000)) + + if accounts.GetBalance([20]byte{4}, 0).String() != "2000" { + t.Fatal("balance of coin ID '0' not equal 2000") + } +} + +func TestAccounts_SubBalance(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + + accounts.SubBalance([20]byte{4}, 0, big.NewInt(500)) + + account := accounts.GetAccount([20]byte{4}) + if account == nil { + t.Fatal("account is nil") + } + if account.getBalance(0).String() != "500" { + t.Fatal("balance of coin ID '0' not equal 500") + } +} + +func TestAccounts_EditMultisig(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + msigAddress := CreateMultisigAddress([20]byte{4}, 12) + + _ = accounts.CreateMultisig([]uint32{3, 3, 6}, []types.Address{[20]byte{1, 1}, [20]byte{2, 3}, [20]byte{3, 3}}, 6, msigAddress) + _ = accounts.EditMultisig(2, []uint32{1, 1, 2}, []types.Address{[20]byte{1}, [20]byte{2}, [20]byte{3}}, msigAddress) + + account := accounts.GetAccount(msigAddress) + if account == nil { + t.Fatal("account is nil") + } + + if !account.IsMultisig() { + t.Fatal("account is not multisig") + } + + multisig := account.Multisig() + if multisig.GetWeight([20]byte{1}) != 1 { + t.Fatal("address weight not equal 1") + } + if multisig.GetWeight([20]byte{2}) != 1 { + t.Fatal("address weight not equal 1") + } + if multisig.GetWeight([20]byte{3}) != 2 { + t.Fatal("address weight not equal 2") + } + if multisig.Threshold != 2 { + t.Fatal("threshold not equal 2") + } +} + +func TestAccounts_Commit(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + + err = accounts.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "8DAE826A26BD8A994B690BD6587A7852B3A75586A1A7162B97479A0D618774EF" { + t.Fatalf("hash %X", hash) + } +} + +func TestAccounts_Export(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + busCoins, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetCoins(coins.NewBus(busCoins)) + b.SetChecker(checker.NewChecker(b)) + accounts, err := NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + accounts.SetBalance([20]byte{4}, 0, big.NewInt(1000)) + + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + coinsState.Create(1, + types.StrToCoinSymbol("AAA"), + "AAACOIN", + helpers.BipToPip(big.NewInt(10)), + 10, + helpers.BipToPip(big.NewInt(10000)), + big.NewInt(0).Exp(big.NewInt(10), big.NewInt(10+18), nil), + nil) + + err = coinsState.Commit() + if err != nil { + t.Fatal(err) + } + + symbol := coinsState.GetCoinBySymbol(types.StrToCoinSymbol("AAA"), 0) + if symbol == nil { + t.Fatal("coin not found") + } + + accounts.SetBalance([20]byte{4}, symbol.ID(), big.NewInt(1001)) + _ = accounts.CreateMultisig([]uint32{1, 1, 2}, []types.Address{[20]byte{1}, [20]byte{2}, [20]byte{3}}, 2, [20]byte{4}) + + err = accounts.Commit() + if err != nil { + t.Fatal(err) + } + + state := new(types.AppState) + accounts.Export(state) + + bytes, err := json.Marshal(state.Accounts) + if err != nil { + t.Fatal(err) + } + + if string(bytes) != "[{\"address\":\"Mx0400000000000000000000000000000000000000\",\"balance\":[{\"coin\":1,\"value\":\"1001\"},{\"coin\":0,\"value\":\"1000\"}],\"nonce\":0,\"multisig_data\":{\"weights\":[1,1,2],\"threshold\":2,\"addresses\":[\"Mx0100000000000000000000000000000000000000\",\"Mx0200000000000000000000000000000000000000\",\"Mx0300000000000000000000000000000000000000\"]}}]" { + t.Fatal("not equal JSON") + } +} diff --git a/core/state/accounts/bus.go b/core/state/accounts/bus.go index 9bbcbc08c..9f758f675 100644 --- a/core/state/accounts/bus.go +++ b/core/state/accounts/bus.go @@ -13,10 +13,6 @@ func NewBus(accounts *Accounts) *Bus { return &Bus{accounts: accounts} } -func (b *Bus) DeleteCoin(address types.Address, coin types.CoinSymbol) { - b.accounts.DeleteCoin(address, coin) -} - -func (b *Bus) AddBalance(address types.Address, coin types.CoinSymbol, value *big.Int) { +func (b *Bus) AddBalance(address types.Address, coin types.CoinID, value *big.Int) { b.accounts.AddBalance(address, coin, value) } diff --git a/core/state/accounts/model.go b/core/state/accounts/model.go index 307773997..b94585d07 100644 --- a/core/state/accounts/model.go +++ b/core/state/accounts/model.go @@ -14,11 +14,11 @@ type Model struct { MultisigData Multisig address types.Address - coins []types.CoinSymbol - balances map[types.CoinSymbol]*big.Int + coins []types.CoinID + balances map[types.CoinID]*big.Int hasDirtyCoins bool - dirtyBalances map[types.CoinSymbol]struct{} + dirtyBalances map[types.CoinID]struct{} isDirty bool // nonce or multisig data isNew bool @@ -27,13 +27,16 @@ type Model struct { } type Multisig struct { - Threshold uint - Weights []uint + Threshold uint32 + Weights []uint32 Addresses []types.Address } -func (m *Multisig) Address() types.Address { - b, err := rlp.EncodeToBytes(m) +func CreateMultisigAddress(owner types.Address, nonce uint64) types.Address { + b, err := rlp.EncodeToBytes(&struct { + Owner types.Address + Nonce uint64 + }{Owner: owner, Nonce: nonce}) if err != nil { panic(err) } @@ -44,7 +47,7 @@ func (m *Multisig) Address() types.Address { return addr } -func (m *Multisig) GetWeight(address types.Address) uint { +func (m *Multisig) GetWeight(address types.Address) uint32 { for i, addr := range m.Addresses { if addr == address { return m.Weights[i] @@ -60,7 +63,7 @@ func (model *Model) setNonce(nonce uint64) { model.markDirty(model.address) } -func (model *Model) getBalance(coin types.CoinSymbol) *big.Int { +func (model *Model) getBalance(coin types.CoinID) *big.Int { return model.balances[coin] } @@ -68,13 +71,13 @@ func (model *Model) hasDirtyBalances() bool { return len(model.dirtyBalances) > 0 } -func (model *Model) isBalanceDirty(coin types.CoinSymbol) bool { +func (model *Model) isBalanceDirty(coin types.CoinID) bool { _, exists := model.dirtyBalances[coin] return exists } -func (model *Model) getOrderedCoins() []types.CoinSymbol { - keys := make([]types.CoinSymbol, 0, len(model.balances)) +func (model *Model) getOrderedCoins() []types.CoinID { + keys := make([]types.CoinID, 0, len(model.balances)) for k := range model.balances { keys = append(keys, k) } @@ -86,13 +89,13 @@ func (model *Model) getOrderedCoins() []types.CoinSymbol { return keys } -func (model *Model) setBalance(coin types.CoinSymbol, amount *big.Int) { +func (model *Model) setBalance(coin types.CoinID, amount *big.Int) { if amount.Cmp(big.NewInt(0)) == 0 { if !model.hasCoin(coin) { return } - var newCoins []types.CoinSymbol + var newCoins []types.CoinID for _, c := range model.coins { if coin == c { continue @@ -119,7 +122,7 @@ func (model *Model) setBalance(coin types.CoinSymbol, amount *big.Int) { model.balances[coin] = amount } -func (model *Model) hasCoin(coin types.CoinSymbol) bool { +func (model *Model) hasCoin(coin types.CoinID) bool { for _, c := range model.coins { if c == coin { return true diff --git a/core/state/app/app.go b/core/state/app/app.go index 204095ee7..2460858f0 100644 --- a/core/state/app/app.go +++ b/core/state/app/app.go @@ -11,15 +11,27 @@ import ( const mainPrefix = 'd' +type RApp interface { + Export(state *types.AppState, height uint64) + GetMaxGas() uint64 + GetTotalSlashed() *big.Int + GetCoinsCount() uint32 + GetNextCoinID() types.CoinID +} + +func (v *App) Tree() tree.ReadOnlyTree { + return v.iavl +} + type App struct { model *Model isDirty bool bus *bus.Bus - iavl tree.Tree + iavl tree.MTree } -func NewApp(stateBus *bus.Bus, iavl tree.Tree) (*App, error) { +func NewApp(stateBus *bus.Bus, iavl tree.MTree) (*App, error) { app := &App{bus: stateBus, iavl: iavl} app.bus.SetApp(NewBus(app)) @@ -35,7 +47,7 @@ func (v *App) Commit() error { data, err := rlp.EncodeToBytes(v.model) if err != nil { - return fmt.Errorf("can't encode app model: %s", err) + return fmt.Errorf("can't encode legacyApp model: %s", err) } path := []byte{mainPrefix} @@ -68,7 +80,7 @@ func (v *App) AddTotalSlashed(amount *big.Int) { model := v.getOrNew() model.setTotalSlashed(big.NewInt(0).Add(model.getTotalSlashed(), amount)) - v.bus.Checker().AddCoin(types.GetBaseCoin(), amount) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), amount) } func (v *App) get() *Model { @@ -84,7 +96,7 @@ func (v *App) get() *Model { model := &Model{} if err := rlp.DecodeBytes(enc, model); err != nil { - panic(fmt.Sprintf("failed to decode app model at: %s", err)) + panic(fmt.Sprintf("failed to decode legacyApp model at: %s", err)) } v.model = model @@ -97,6 +109,7 @@ func (v *App) getOrNew() *Model { if model == nil { model = &Model{ TotalSlashed: big.NewInt(0), + CoinsCount: 0, MaxGas: 0, markDirty: v.markDirty, } @@ -114,6 +127,18 @@ func (v *App) SetTotalSlashed(amount *big.Int) { v.getOrNew().setTotalSlashed(amount) } +func (v *App) GetCoinsCount() uint32 { + return v.getOrNew().getCoinsCount() +} + +func (v *App) GetNextCoinID() types.CoinID { + return types.CoinID(v.GetCoinsCount() + 1) +} + +func (v *App) SetCoinsCount(count uint32) { + v.getOrNew().setCoinsCount(count) +} + func (v *App) Export(state *types.AppState, height uint64) { state.MaxGas = v.GetMaxGas() state.TotalSlashed = v.GetTotalSlashed().String() diff --git a/core/state/app/model.go b/core/state/app/model.go index d502226f7..3ca80ec15 100644 --- a/core/state/app/model.go +++ b/core/state/app/model.go @@ -4,6 +4,7 @@ import "math/big" type Model struct { TotalSlashed *big.Int + CoinsCount uint32 MaxGas uint64 markDirty func() @@ -34,3 +35,15 @@ func (model *Model) setTotalSlashed(totalSlashed *big.Int) { } model.TotalSlashed = totalSlashed } + +func (model *Model) getCoinsCount() uint32 { + return model.CoinsCount +} + +func (model *Model) setCoinsCount(count uint32) { + if model.CoinsCount != count { + model.markDirty() + } + + model.CoinsCount = count +} diff --git a/core/state/bus/accounts.go b/core/state/bus/accounts.go index 888f55bc6..f584503e8 100644 --- a/core/state/bus/accounts.go +++ b/core/state/bus/accounts.go @@ -6,6 +6,5 @@ import ( ) type Accounts interface { - DeleteCoin(types.Address, types.CoinSymbol) - AddBalance(types.Address, types.CoinSymbol, *big.Int) + AddBalance(types.Address, types.CoinID, *big.Int) } diff --git a/core/state/bus/bus.go b/core/state/bus/bus.go index 699d011bd..d19fb4d42 100644 --- a/core/state/bus/bus.go +++ b/core/state/bus/bus.go @@ -1,6 +1,6 @@ package bus -import eventsdb "github.com/MinterTeam/events-db" +import eventsdb "github.com/MinterTeam/minter-go-node/core/events" type Bus struct { coins Coins @@ -8,6 +8,8 @@ type Bus struct { accounts Accounts candidates Candidates frozenfunds FrozenFunds + halts HaltBlocks + waitlist WaitList events eventsdb.IEventsDB checker Checker } @@ -56,6 +58,22 @@ func (b *Bus) FrozenFunds() FrozenFunds { return b.frozenfunds } +func (b *Bus) SetHaltBlocks(halts HaltBlocks) { + b.halts = halts +} + +func (b *Bus) Halts() HaltBlocks { + return b.halts +} + +func (b *Bus) SetWaitList(waitList WaitList) { + b.waitlist = waitList +} + +func (b *Bus) WaitList() WaitList { + return b.waitlist +} + func (b *Bus) SetEvents(events eventsdb.IEventsDB) { b.events = events } diff --git a/core/state/bus/candidates.go b/core/state/bus/candidates.go index c1c90c1f1..8049c190b 100644 --- a/core/state/bus/candidates.go +++ b/core/state/bus/candidates.go @@ -16,14 +16,16 @@ type Candidates interface { type Stake struct { Owner types.Address Value *big.Int - Coin types.CoinSymbol + Coin types.CoinID BipValue *big.Int } type Candidate struct { - PubKey types.Pubkey - RewardAddress types.Address - OwnerAddress types.Address - Commission uint - Status byte + ID uint32 + PubKey types.Pubkey + RewardAddress types.Address + OwnerAddress types.Address + ControlAddress types.Address + Commission uint32 + Status byte } diff --git a/core/state/bus/checker.go b/core/state/bus/checker.go index 5c4390be3..b110cc7c5 100644 --- a/core/state/bus/checker.go +++ b/core/state/bus/checker.go @@ -6,6 +6,6 @@ import ( ) type Checker interface { - AddCoin(types.CoinSymbol, *big.Int, ...string) - AddCoinVolume(types.CoinSymbol, *big.Int) + AddCoin(types.CoinID, *big.Int, ...string) + AddCoinVolume(types.CoinID, *big.Int) } diff --git a/core/state/bus/coins.go b/core/state/bus/coins.go index 0a25a10cf..9c5d9534f 100644 --- a/core/state/bus/coins.go +++ b/core/state/bus/coins.go @@ -1,20 +1,31 @@ package bus import ( + "fmt" "github.com/MinterTeam/minter-go-node/core/types" "math/big" ) type Coins interface { - GetCoin(types.CoinSymbol) *Coin - SubCoinVolume(types.CoinSymbol, *big.Int) - SubCoinReserve(types.CoinSymbol, *big.Int) + GetCoin(types.CoinID) *Coin + SubCoinVolume(types.CoinID, *big.Int) + SubCoinReserve(types.CoinID, *big.Int) } type Coin struct { + ID types.CoinID Name string - Crr uint + Crr uint32 Symbol types.CoinSymbol + Version types.CoinVersion Volume *big.Int Reserve *big.Int } + +func (m Coin) GetFullSymbol() string { + if m.Version == 0 { + return m.Symbol.String() + } + + return fmt.Sprintf("%s-%d", m.Symbol, m.Version) +} diff --git a/core/state/bus/frozenfunds.go b/core/state/bus/frozenfunds.go index 2ec11f946..250730505 100644 --- a/core/state/bus/frozenfunds.go +++ b/core/state/bus/frozenfunds.go @@ -6,5 +6,5 @@ import ( ) type FrozenFunds interface { - AddFrozenFund(uint64, types.Address, types.Pubkey, types.CoinSymbol, *big.Int) + AddFrozenFund(uint64, types.Address, types.Pubkey, uint32, types.CoinID, *big.Int) } diff --git a/core/state/bus/halts.go b/core/state/bus/halts.go new file mode 100644 index 000000000..732ead868 --- /dev/null +++ b/core/state/bus/halts.go @@ -0,0 +1,7 @@ +package bus + +import "github.com/MinterTeam/minter-go-node/core/types" + +type HaltBlocks interface { + AddHaltBlock(uint64, types.Pubkey) +} diff --git a/core/state/bus/waitlist.go b/core/state/bus/waitlist.go new file mode 100644 index 000000000..25ae9e707 --- /dev/null +++ b/core/state/bus/waitlist.go @@ -0,0 +1,10 @@ +package bus + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" +) + +type WaitList interface { + AddToWaitList(address types.Address, pubkey types.Pubkey, coin types.CoinID, value *big.Int) +} diff --git a/core/state/candidates/bus.go b/core/state/candidates/bus.go index f128d08e6..8585a099a 100644 --- a/core/state/candidates/bus.go +++ b/core/state/candidates/bus.go @@ -14,6 +14,7 @@ func NewBus(candidates *Candidates) *Bus { return &Bus{candidates: candidates} } +// GetStakes returns list of stakes of candidate with given public key func (b *Bus) GetStakes(pubkey types.Pubkey) []bus.Stake { stakes := b.candidates.GetStakes(pubkey) var result []bus.Stake @@ -30,10 +31,12 @@ func (b *Bus) GetStakes(pubkey types.Pubkey) []bus.Stake { return result } +// Punish punished a candidate with given tendermint-address func (b *Bus) Punish(height uint64, address types.TmAddress) *big.Int { return b.candidates.Punish(height, address) } +// GetCandidate returns candidate by a public key func (b *Bus) GetCandidate(pubkey types.Pubkey) *bus.Candidate { candidate := b.candidates.GetCandidate(pubkey) if candidate == nil { @@ -41,18 +44,22 @@ func (b *Bus) GetCandidate(pubkey types.Pubkey) *bus.Candidate { } return &bus.Candidate{ - PubKey: candidate.PubKey, - RewardAddress: candidate.RewardAddress, - OwnerAddress: candidate.OwnerAddress, - Commission: candidate.Commission, - Status: candidate.Status, + ID: candidate.ID, + PubKey: candidate.PubKey, + RewardAddress: candidate.RewardAddress, + OwnerAddress: candidate.OwnerAddress, + ControlAddress: candidate.ControlAddress, + Commission: candidate.Commission, + Status: candidate.Status, } } +// SetOffline sets candidate status to CandidateStatusOffline func (b *Bus) SetOffline(pubkey types.Pubkey) { b.candidates.SetOffline(pubkey) } +// GetCandidateByTendermintAddress finds and returns candidate with given tendermint-address func (b *Bus) GetCandidateByTendermintAddress(tmAddress types.TmAddress) *bus.Candidate { candidate := b.candidates.GetCandidateByTendermintAddress(tmAddress) if candidate == nil { @@ -60,10 +67,12 @@ func (b *Bus) GetCandidateByTendermintAddress(tmAddress types.TmAddress) *bus.Ca } return &bus.Candidate{ - PubKey: candidate.PubKey, - RewardAddress: candidate.RewardAddress, - OwnerAddress: candidate.OwnerAddress, - Commission: candidate.Commission, - Status: candidate.Status, + ID: candidate.ID, + PubKey: candidate.PubKey, + RewardAddress: candidate.RewardAddress, + OwnerAddress: candidate.OwnerAddress, + ControlAddress: candidate.ControlAddress, + Commission: candidate.Commission, + Status: candidate.Status, } } diff --git a/core/state/candidates/candidate_test.go b/core/state/candidates/candidate_test.go new file mode 100644 index 000000000..6179cf063 --- /dev/null +++ b/core/state/candidates/candidate_test.go @@ -0,0 +1,1243 @@ +package candidates + +import ( + "encoding/json" + "fmt" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" + "github.com/MinterTeam/minter-go-node/core/state/accounts" + "github.com/MinterTeam/minter-go-node/core/state/app" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/checker" + "github.com/MinterTeam/minter-go-node/core/state/coins" + "github.com/MinterTeam/minter-go-node/core/state/waitlist" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/tree" + "github.com/tendermint/tendermint/crypto/ed25519" + db "github.com/tendermint/tm-db" + "math/big" + "strconv" + "testing" +) + +func TestCandidates_Create_oneCandidate(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + + if candidates.PubKey(candidate.ID) != [32]byte{4} { + t.Fatal("candidate error ID or PubKey") + } +} + +func TestCandidates_Commit_createThreeCandidates(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.Create([20]byte{11}, [20]byte{21}, [20]byte{31}, [32]byte{41}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "D7A17D41EAE39D61D3F85BC3311DA1FE306E885FF03024D0173F23E3739E719B" { + t.Fatalf("hash %X", hash) + } + + candidates.Create([20]byte{1, 1}, [20]byte{2, 2}, [20]byte{3, 3}, [32]byte{4, 4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 2 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "01E34A08A0CF18403B8C3708FA773A4D0B152635F321085CE7B68F04FD520A9A" { + t.Fatalf("hash %X", hash) + } +} + +func TestCandidates_Commit_changePubKeyAndCheckBlockList(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.Create([20]byte{11}, [20]byte{21}, [20]byte{31}, [32]byte{41}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "D7A17D41EAE39D61D3F85BC3311DA1FE306E885FF03024D0173F23E3739E719B" { + t.Fatalf("hash %X", hash) + } + + candidates.ChangePubKey([32]byte{4}, [32]byte{5}) + candidates.ChangePubKey([32]byte{41}, [32]byte{6}) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 2 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "BB335E1AA631D9540C2CB0AC9C959B556C366B79D39B828B07106CF2DACE5A2D" { + t.Fatalf("hash %X", hash) + } + + if !candidates.IsBlockedPubKey([32]byte{4}) { + t.Fatal("pub_key is not blocked") + } + + candidates, err = NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.LoadCandidates() + candidate := candidates.GetCandidate([32]byte{5}) + if candidate == nil { + t.Fatal("candidate not found") + } + var pubkey ed25519.PubKeyEd25519 + copy(pubkey[:], types.Pubkey{5}.Bytes()) + var address types.TmAddress + copy(address[:], pubkey.Address().Bytes()) + if *(candidate.tmAddress) != address { + t.Fatal("tmAddress not change") + } + if candidates.PubKey(candidate.ID) != [32]byte{5} { + t.Fatal("candidate map ids and pubKeys invalid") + } + +} +func TestCandidates_AddToBlockPubKey(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.AddToBlockPubKey([32]byte{4}) + + if !candidates.IsBlockedPubKey([32]byte{4}) { + t.Fatal("pub_key is not blocked") + } +} + +func TestCandidates_Commit_withStakeAndUpdate(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "FCF3853839873D3EC344016C04A5E75166F51063745670DF5D561C060E7F45A1" { + t.Fatalf("hash %X", hash) + } + + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, []types.Stake{ + { + Owner: [20]byte{2}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 2 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "2D206158AA79C3BDAA019C61FEAD47BB9B6170C445EE7B36E935AC954765E99F" { + t.Fatalf("hash %X", hash) + } +} + +func TestCandidates_Commit_edit(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "FCF3853839873D3EC344016C04A5E75166F51063745670DF5D561C060E7F45A1" { + t.Fatalf("hash %X", hash) + } + + candidates.Edit([32]byte{4}, [20]byte{1, 1}, [20]byte{2, 2}, [20]byte{3, 3}) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 2 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "482BE887F2E18DC1BB829BD6AFE8887CE4EC74D4DC485DB1355D78093EAB6B35" { + t.Fatalf("hash %X", hash) + } + + if candidates.GetCandidateControl([32]byte{4}) != [20]byte{3, 3} { + t.Fatal("control address is not change") + } + + if candidates.GetCandidateOwner([32]byte{4}) != [20]byte{2, 2} { + t.Fatal("owner address is not change") + } + +} + +func TestCandidates_Commit_createOneCandidateWithID(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.CreateWithID([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10, 1) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "FCF3853839873D3EC344016C04A5E75166F51063745670DF5D561C060E7F45A1" { + t.Fatalf("hash %X", hash) + } + + id := candidates.ID([32]byte{4}) + if id != 1 { + t.Fatalf("ID %d", id) + } +} + +func TestCandidates_Commit_Delegate(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "FCF3853839873D3EC344016C04A5E75166F51063745670DF5D561C060E7F45A1" { + t.Fatalf("hash %X", hash) + } + + candidates.Delegate([20]byte{1, 1}, [32]byte{4}, 0, big.NewInt(10000000), big.NewInt(10000000)) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 2 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "43FE25EB54D52C6516521FB0F951E87359040A9E8DAA23BDC27C6EC5DFBC10EF" { + t.Fatalf("hash %X", hash) + } +} + +func TestCandidates_SetOnlineAndBusSetOffline(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + candidates.SetOnline([32]byte{4}) + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + if candidate.Status != CandidateStatusOnline { + t.Fatal("candidate not change status to online") + } + candidates.bus.Candidates().SetOffline([32]byte{4}) + if candidate.Status != CandidateStatusOffline { + t.Fatal("candidate not change status to offline") + } +} + +func TestCandidates_Count(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.Create([20]byte{1, 1}, [20]byte{2, 2}, [20]byte{3, 3}, [32]byte{4, 4}, 20) + candidates.Create([20]byte{1, 1, 1}, [20]byte{2, 2, 2}, [20]byte{3, 3, 3}, [32]byte{4, 4, 4}, 30) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "25F7EF5A007B3D8A5FB4DCE32F9DBC28C2AE6848B893986E3055BC3045E8F00F" { + t.Fatalf("hash %X", hash) + } + + count := candidates.Count() + if count != 3 { + t.Fatalf("coun %d", count) + } +} + +func TestCandidates_GetTotalStake_fromModelAndFromDB(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + wl, err := waitlist.NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetWaitList(waitlist.NewBus(wl)) + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + var stakes []types.Stake + for i := 0; i < 1010; i++ { + value := strconv.Itoa(i + 2000) + stakes = append(stakes, types.Stake{ + Owner: types.StringToAddress(strconv.Itoa(i)), + Coin: 0, + Value: value, + BipValue: value, + }) + } + candidates.SetStakes([32]byte{4}, stakes, []types.Stake{ + { + Owner: [20]byte{2}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + { + Owner: types.StringToAddress("1"), + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + + candidates.RecalculateStakes(0) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + totalStake := candidates.GetTotalStake([32]byte{4}) + totalStakeString := totalStake.String() + if totalStakeString != "2509591" { + t.Fatalf("total stake %s", totalStakeString) + } + + candidates, err = NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + candidates.LoadCandidates() + candidates.GetCandidate([32]byte{4}).totalBipStake = nil + totalStake = candidates.GetTotalStake([32]byte{4}) + totalStakeString = totalStake.String() + if totalStakeString != "2509591" { + t.Fatalf("total stake %s", totalStakeString) + } +} + +func TestCandidates_Export(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.AddToBlockPubKey([32]byte{10}) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, []types.Stake{ + { + Owner: [20]byte{2}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + candidates.recalculateStakes(0) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + state := new(types.AppState) + candidates.Export(state) + + bytes, err := json.Marshal(state.Candidates) + if err != nil { + t.Fatal(err) + } + + if string(bytes) != "[{\"id\":1,\"reward_address\":\"Mx0200000000000000000000000000000000000000\",\"owner_address\":\"Mx0100000000000000000000000000000000000000\",\"control_address\":\"Mx0300000000000000000000000000000000000000\",\"total_bip_stake\":\"200\",\"public_key\":\"Mp0400000000000000000000000000000000000000000000000000000000000000\",\"commission\":10,\"stakes\":[{\"owner\":\"Mx0100000000000000000000000000000000000000\",\"coin\":0,\"value\":\"100\",\"bip_value\":\"100\"},{\"owner\":\"Mx0200000000000000000000000000000000000000\",\"coin\":0,\"value\":\"100\",\"bip_value\":\"100\"}],\"updates\":[],\"status\":1}]" { + t.Fatal("not equal JSON") + } + + bytes, err = json.Marshal(state.BlockListCandidates) + if err != nil { + t.Fatal(err) + } + + if string(bytes) != "[\"Mp0a00000000000000000000000000000000000000000000000000000000000000\"]" { + t.Fatal("not equal JSON") + } +} + +func TestCandidates_busGetStakes(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, []types.Stake{ + { + Owner: [20]byte{2}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + stakes := candidates.bus.Candidates().GetStakes([32]byte{4}) + if len(stakes) != 1 { + t.Fatalf("stakes count %d", len(stakes)) + } + + if stakes[0].Owner != [20]byte{1} { + t.Fatal("owner is invalid") + } +} + +func TestCandidates_GetCandidateByTendermintAddress(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + + candidateByTmAddr := candidates.GetCandidateByTendermintAddress(candidate.GetTmAddress()) + if candidate.ID != candidateByTmAddr.ID { + t.Fatal("candidate ID != candidateByTmAddr.ID") + } +} +func TestCandidates_busGetCandidateByTendermintAddress(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + candidates, err := NewCandidates(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + + candidateByTmAddr := candidates.bus.Candidates().GetCandidateByTendermintAddress(candidate.GetTmAddress()) + if candidate.ID != candidateByTmAddr.ID { + t.Fatal("candidate ID != candidateByTmAddr.ID") + } +} + +func TestCandidates_Punish(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + wl, err := waitlist.NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + b.SetWaitList(waitlist.NewBus(wl)) + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + appBus, err := app.NewApp(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetApp(appBus) + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + coinsState.Create(1, + types.StrToCoinSymbol("AAA"), + "AAACOIN", + helpers.BipToPip(big.NewInt(10)), + 10, + helpers.BipToPip(big.NewInt(10000)), + big.NewInt(0).Exp(big.NewInt(10), big.NewInt(10+18), nil), + nil) + + err = coinsState.Commit() + if err != nil { + t.Fatal(err) + } + + symbol := coinsState.GetCoinBySymbol(types.StrToCoinSymbol("AAA"), 0) + if symbol == nil { + t.Fatal("coin not found") + } + + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + { + Owner: [20]byte{1}, + Coin: uint64(symbol.ID()), + Value: "100", + BipValue: "0", + }, + }, nil) + + candidates.RecalculateStakes(1) + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + candidates.bus.Candidates().Punish(0, candidate.GetTmAddress()) + + if candidate.stakesCount != 2 { + t.Fatalf("stakes count %d", candidate.stakesCount) + } + + if candidate.stakes[0].Value.String() != "99" { + t.Fatalf("stakes[0] == %s", candidate.stakes[0].Value.String()) + } +} + +type fr struct { + unbounds []*big.Int +} + +func (fr *fr) AddFrozenFund(_ uint64, _ types.Address, _ types.Pubkey, _ uint32, _ types.CoinID, value *big.Int) { + fr.unbounds = append(fr.unbounds, value) +} +func TestCandidates_PunishByzantineCandidate(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + frozenfunds := &fr{} + b.SetFrozenFunds(frozenfunds) + wl, err := waitlist.NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + b.SetWaitList(waitlist.NewBus(wl)) + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + appBus, err := app.NewApp(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetApp(appBus) + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + coinsState.Create(1, + types.StrToCoinSymbol("AAA"), + "AAACOIN", + helpers.BipToPip(big.NewInt(10)), + 10, + helpers.BipToPip(big.NewInt(10000)), + big.NewInt(0).Exp(big.NewInt(10), big.NewInt(10+18), nil), + nil) + + err = coinsState.Commit() + if err != nil { + t.Fatal(err) + } + + symbol := coinsState.GetCoinBySymbol(types.StrToCoinSymbol("AAA"), 0) + if symbol == nil { + t.Fatal("coin not found") + } + + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + { + Owner: [20]byte{1}, + Coin: uint64(symbol.ID()), + Value: "100", + BipValue: "0", + }, + }, nil) + + candidates.RecalculateStakes(1) + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + candidates.PunishByzantineCandidate(0, candidate.GetTmAddress()) + + if candidates.GetStakeValueOfAddress([32]byte{4}, [20]byte{1}, symbol.ID()).String() != "0" { + t.Error("stake[0] not unbound") + } + if candidates.GetStakeValueOfAddress([32]byte{4}, [20]byte{1}, 0).String() != "0" { + t.Error("stake[1] not unbound") + } + + if len(frozenfunds.unbounds) != 2 { + t.Fatalf("count unbounds == %d", len(frozenfunds.unbounds)) + } + + if frozenfunds.unbounds[0].String() != "95" { + t.Fatalf("frozenfunds.unbounds[0] == %s", frozenfunds.unbounds[0].String()) + } + if frozenfunds.unbounds[1].String() != "95" { + t.Fatalf("frozenfunds.unbounds[1] == %s", frozenfunds.unbounds[1].String()) + } +} + +func TestCandidates_SubStake(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, nil) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + candidates.SubStake([20]byte{1}, [32]byte{4}, 0, big.NewInt(10)) + stake := candidates.GetStakeOfAddress([32]byte{4}, [20]byte{1}, 0) + if stake == nil { + t.Fatal("stake not found") + } + + if stake.Value.String() != "90" { + t.Fatal("sub stake error") + } +} + +func TestCandidates_IsNewCandidateStakeSufficient(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, nil) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + if !candidates.IsNewCandidateStakeSufficient(0, big.NewInt(1000), 1) { + t.Log("is not new candidate stake sufficient") + } +} + +func TestCandidates_IsDelegatorStakeSufficient(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + wl, err := waitlist.NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetWaitList(waitlist.NewBus(wl)) + b.SetChecker(checker.NewChecker(b)) + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + + var stakes []types.Stake + for i := 0; i < 1010; i++ { + value := strconv.Itoa(i + 2000) + stakes = append(stakes, types.Stake{ + Owner: types.StringToAddress(strconv.Itoa(i)), + Coin: 0, + Value: value, + BipValue: value, + }) + } + candidates.SetStakes([32]byte{4}, stakes, []types.Stake{ + { + Owner: [20]byte{2}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: types.StringToAddress("10000"), + Coin: 0, + Value: "10000", + BipValue: "10000", + }, + }, nil) + + candidates.recalculateStakes(0) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + if candidates.IsDelegatorStakeSufficient([20]byte{1}, [32]byte{4}, 0, big.NewInt(10)) { + t.Fatal("is not delegator stake sufficient") + } +} +func TestCandidates_IsDelegatorStakeSufficient_false(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, nil) + + candidates.recalculateStakes(0) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + if !candidates.IsDelegatorStakeSufficient([20]byte{1}, [32]byte{4}, 0, big.NewInt(10)) { + t.Fatal("is delegator stake sufficient") + } +} + +func TestCandidates_GetNewCandidates(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "1000000000000000000000", + BipValue: "1000000000000000000000", + }, + }, nil) + candidates.SetOnline([32]byte{4}) + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{5}, 10) + candidates.SetStakes([32]byte{5}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "1000000000000000000000", + BipValue: "1000000000000000000000", + }, + }, nil) + candidates.SetOnline([32]byte{5}) + + candidates.RecalculateStakes(1) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + newCandidates := candidates.GetNewCandidates(2) + if len(newCandidates) != 2 { + t.Fatal("error count of new candidates") + } +} + +func TestCandidate_GetFilteredUpdates(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidates.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + { + Owner: [20]byte{1}, + Coin: 0, + Value: "100", + BipValue: "100", + }, + }) + err = candidates.Commit() + if err != nil { + t.Fatal(err) + } + + candidate := candidates.GetCandidate([32]byte{4}) + if candidate == nil { + t.Fatal("candidate not found") + } + + candidate.filterUpdates() + + if len(candidate.updates) != 1 { + t.Fatal("updates not merged") + } + + if candidate.updates[0].Value.String() != "200" { + t.Fatal("error merge updates") + } +} + +func TestCandidates_CalculateBipValue_RecalculateStakes_GetTotalStake(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + busCoins, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetCoins(coins.NewBus(busCoins)) + candidates, err := NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidates.Create([20]byte{1}, [20]byte{1}, [20]byte{1}, [32]byte{1}, 1) + candidates.SetStakes([32]byte{1}, []types.Stake{ + { + Owner: types.Address{1}, + Coin: 52, + Value: "27331500301898443574821601", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "26788352158593847436109305", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "23056159980819190092008573", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "11588709101209768903338862", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "10699458018244407488345007", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "10178615801247206484340203", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "9695040709408605598614475", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "9311613733840163086812673", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "8035237015568850680085714", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "7751636678470495902806639", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "7729118857616059555215844", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "7246351659896715230790480", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "5634000000000000000000000", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "5111293424492290525817483", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "4636302767358508700208179", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "4375153667350433703873779", + BipValue: "0", + }, + { + Owner: types.Address{1}, + Coin: 52, + Value: "6468592759016388938414535", + BipValue: "0", + }, + }, nil) + volume, _ := big.NewInt(0).SetString("235304453408778922901904166", 10) + reserve, _ := big.NewInt(0).SetString("3417127836274022127064945", 10) + maxSupply, _ := big.NewInt(0).SetString("1000000000000000000000000000000000", 10) + coinsState.Create(52, + types.StrToCoinSymbol("ONLY1"), + "ONLY1", + volume, + 70, + reserve, + maxSupply, + nil) + + amount, _ := big.NewInt(0).SetString("407000000000000000000000", 10) + cache := newCoinsCache() + + bipValue := candidates.calculateBipValue(52, amount, false, true, cache) + if bipValue.Sign() < 0 { + t.Fatalf("%s", bipValue.String()) + } + bipValue = candidates.calculateBipValue(52, amount, false, true, cache) + if bipValue.Sign() < 0 { + t.Fatalf("%s", bipValue.String()) + } + + candidates.RecalculateStakes(0) + totalStake := candidates.GetTotalStake([32]byte{1}) + if totalStake.String() != "2435386873327199834002556" { + t.Fatalf("total stake %s", totalStake.String()) + } +} diff --git a/core/state/candidates/candidates.go b/core/state/candidates/candidates.go index 4f429572f..dd3627e08 100644 --- a/core/state/candidates/candidates.go +++ b/core/state/candidates/candidates.go @@ -2,50 +2,104 @@ package candidates import ( "bytes" + "encoding/binary" "fmt" - eventsdb "github.com/MinterTeam/events-db" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/state/bus" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" - "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" "sort" "sync" ) +// Common constants const ( CandidateStatusOffline = 0x01 CandidateStatusOnline = 0x02 UnbondPeriod = 518400 MaxDelegatorsPerCandidate = 1000 +) +const ( mainPrefix = 'c' + pubKeyIDPrefix = mainPrefix + 'p' + blockListPrefix = mainPrefix + 'b' + maxIDPrefix = mainPrefix + 'i' stakesPrefix = 's' totalStakePrefix = 't' updatesPrefix = 'u' ) +var ( + minValidatorBipStake = helpers.BipToPip(big.NewInt(1000)) +) + +// RCandidates interface represents Candidates state +type RCandidates interface { + Export(state *types.AppState) + Exists(pubkey types.Pubkey) bool + IsBlockedPubKey(pubkey types.Pubkey) bool + PubKey(id uint32) types.Pubkey + Count() int + IsNewCandidateStakeSufficient(coin types.CoinID, stake *big.Int, limit int) bool + IsDelegatorStakeSufficient(address types.Address, pubkey types.Pubkey, coin types.CoinID, amount *big.Int) bool + GetStakeValueOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinID) *big.Int + GetCandidateOwner(pubkey types.Pubkey) types.Address + GetCandidateControl(pubkey types.Pubkey) types.Address + GetTotalStake(pubkey types.Pubkey) *big.Int + LoadCandidates() + LoadStakesOfCandidate(pubkey types.Pubkey) + GetCandidate(pubkey types.Pubkey) *Candidate + LoadStakes() + GetCandidates() []*Candidate + GetStakes(pubkey types.Pubkey) []*stake +} + +// Candidates struct is a store of Candidates state type Candidates struct { - list map[types.Pubkey]*Candidate + list map[uint32]*Candidate - iavl tree.Tree + isDirty bool + blockList map[types.Pubkey]struct{} + pubKeyIDs map[types.Pubkey]uint32 + maxID uint32 + + iavl tree.MTree bus *bus.Bus - lock sync.RWMutex - loaded bool + lock sync.RWMutex + loaded bool + isChangedPublicKeys bool } -func NewCandidates(bus *bus.Bus, iavl tree.Tree) (*Candidates, error) { - candidates := &Candidates{iavl: iavl, bus: bus} +func (c *Candidates) IsChangedPublicKeys() bool { + return c.isChangedPublicKeys +} +func (c *Candidates) ResetIsChangedPublicKeys() { + c.isChangedPublicKeys = false +} + +// NewCandidates returns newly created Candidates state with a given bus and iavl +func NewCandidates(bus *bus.Bus, iavl tree.MTree) (*Candidates, error) { + candidates := &Candidates{ + iavl: iavl, + bus: bus, + blockList: map[types.Pubkey]struct{}{}, + pubKeyIDs: map[types.Pubkey]uint32{}, + list: map[uint32]*Candidate{}, + } candidates.bus.SetCandidates(NewBus(candidates)) return candidates, nil } +// Commit writes changes to iavl, may return an error func (c *Candidates) Commit() error { keys := c.getOrderedCandidates() @@ -71,13 +125,48 @@ func (c *Candidates) Commit() error { c.iavl.Set(path, data) } + if c.isDirty { + c.isDirty = false + var pubIDs []pubkeyID + for pk, v := range c.pubKeyIDs { + pubIDs = append(pubIDs, pubkeyID{ + PubKey: pk, + ID: v, + }) + } + sort.SliceStable(pubIDs, func(i, j int) bool { + return pubIDs[i].ID < pubIDs[j].ID + }) + pubIDData, err := rlp.EncodeToBytes(pubIDs) + if err != nil { + panic(fmt.Sprintf("failed to encode candidates public key with ID: %s", err)) + } + + c.iavl.Set([]byte{pubKeyIDPrefix}, pubIDData) + + var blockList []types.Pubkey + for pubKey := range c.blockList { + blockList = append(blockList, pubKey) + } + sort.SliceStable(blockList, func(i, j int) bool { + return bytes.Compare(blockList[i].Bytes(), blockList[j].Bytes()) == 1 + }) + blockListData, err := rlp.EncodeToBytes(blockList) + if err != nil { + return fmt.Errorf("can't encode block list of candidates: %v", err) + } + c.iavl.Set([]byte{blockListPrefix}, blockListData) + + c.iavl.Set([]byte{maxIDPrefix}, c.maxIDBytes()) + } + for _, pubkey := range keys { candidate := c.getFromMap(pubkey) candidate.isDirty = false if candidate.isTotalStakeDirty { path := []byte{mainPrefix} - path = append(path, pubkey[:]...) + path = append(path, candidate.idBytes()...) path = append(path, totalStakePrefix) c.iavl.Set(path, candidate.totalBipStake.Bytes()) candidate.isTotalStakeDirty = false @@ -91,7 +180,7 @@ func (c *Candidates) Commit() error { candidate.dirtyStakes[index] = false path := []byte{mainPrefix} - path = append(path, pubkey[:]...) + path = append(path, candidate.idBytes()...) path = append(path, stakesPrefix) path = append(path, []byte(fmt.Sprintf("%d", index))...) @@ -116,7 +205,7 @@ func (c *Candidates) Commit() error { } path := []byte{mainPrefix} - path = append(path, pubkey[:]...) + path = append(path, candidate.idBytes()...) path = append(path, updatesPrefix) c.iavl.Set(path, data) candidate.isUpdatesDirty = false @@ -126,6 +215,9 @@ func (c *Candidates) Commit() error { return nil } +// GetNewCandidates returns list of candidates that can be the new validators +// Skips offline candidates and candidates with stake less than minValidatorBipStake +// Result is sorted by candidates stakes and limited to valCount func (c *Candidates) GetNewCandidates(valCount int) []Candidate { var result []Candidate @@ -135,7 +227,7 @@ func (c *Candidates) GetNewCandidates(valCount int) []Candidate { continue } - if candidate.totalBipStake.Cmp(big.NewInt(0)) == 0 { + if candidate.totalBipStake.Cmp(minValidatorBipStake) == -1 { continue } @@ -153,15 +245,18 @@ func (c *Candidates) GetNewCandidates(valCount int) []Candidate { return result } -func (c *Candidates) Create(ownerAddress types.Address, rewardAddress types.Address, pubkey types.Pubkey, commission uint) { +// Create creates a new candidate with given params and adds it to state +func (c *Candidates) Create(ownerAddress, rewardAddress, controlAddress types.Address, pubkey types.Pubkey, commission uint32) { candidate := &Candidate{ + ID: 0, PubKey: pubkey, RewardAddress: rewardAddress, OwnerAddress: ownerAddress, + ControlAddress: controlAddress, Commission: commission, Status: CandidateStatusOffline, totalBipStake: big.NewInt(0), - stakes: [MaxDelegatorsPerCandidate]*Stake{}, + stakes: [MaxDelegatorsPerCandidate]*stake{}, isDirty: true, isTotalStakeDirty: true, } @@ -170,6 +265,16 @@ func (c *Candidates) Create(ownerAddress types.Address, rewardAddress types.Addr c.setToMap(pubkey, candidate) } +// CreateWithID creates a new candidate with given params and adds it to state +// CreateWithID uses given ID to be associated with public key of a candidate +func (c *Candidates) CreateWithID(ownerAddress, rewardAddress, controlAddress types.Address, pubkey types.Pubkey, commission uint32, id uint32) { + c.setPubKeyID(pubkey, id) + c.Create(ownerAddress, rewardAddress, controlAddress, pubkey, commission) +} + +// PunishByzantineCandidate finds candidate with given tmAddress and punishes it: +// 1. Subs 5% of each stake of a candidate +// 2. Unbond each stake of a candidate func (c *Candidates) PunishByzantineCandidate(height uint64, tmAddress types.TmAddress) { candidate := c.GetCandidateByTendermintAddress(tmAddress) stakes := c.GetStakes(candidate.PubKey) @@ -186,8 +291,8 @@ func (c *Candidates) PunishByzantineCandidate(height uint64, tmAddress types.TmA coin := c.bus.Coins().GetCoin(stake.Coin) ret := formula.CalculateSaleReturn(coin.Volume, coin.Reserve, coin.Crr, slashed) - c.bus.Coins().SubCoinVolume(coin.Symbol, slashed) - c.bus.Coins().SubCoinReserve(coin.Symbol, ret) + c.bus.Coins().SubCoinVolume(coin.ID, slashed) + c.bus.Coins().SubCoinReserve(coin.ID, ret) c.bus.App().AddTotalSlashed(ret) c.bus.Checker().AddCoin(stake.Coin, big.NewInt(0).Neg(ret)) @@ -196,18 +301,19 @@ func (c *Candidates) PunishByzantineCandidate(height uint64, tmAddress types.TmA c.bus.Checker().AddCoin(stake.Coin, big.NewInt(0).Neg(slashed)) } - c.bus.Events().AddEvent(uint32(height), eventsdb.SlashEvent{ + c.bus.Events().AddEvent(uint32(height), &eventsdb.SlashEvent{ Address: stake.Owner, Amount: slashed.String(), - Coin: stake.Coin, + Coin: uint64(stake.Coin), ValidatorPubKey: candidate.PubKey, }) - c.bus.FrozenFunds().AddFrozenFund(height+UnbondPeriod, stake.Owner, candidate.PubKey, stake.Coin, newValue) + c.bus.FrozenFunds().AddFrozenFund(height+UnbondPeriod, stake.Owner, candidate.PubKey, candidate.ID, stake.Coin, newValue) stake.setValue(big.NewInt(0)) } } +// GetCandidateByTendermintAddress finds and returns candidate with given tendermint-address func (c *Candidates) GetCandidateByTendermintAddress(address types.TmAddress) *Candidate { candidates := c.GetCandidates() for _, candidate := range candidates { @@ -219,217 +325,14 @@ func (c *Candidates) GetCandidateByTendermintAddress(address types.TmAddress) *C return nil } +// RecalculateStakes recalculate stakes of all candidates: +// 1. Updates bip-values of each stake +// 2. Applies updates func (c *Candidates) RecalculateStakes(height uint64) { - if height >= upgrades.UpgradeBlock3 { - c.recalculateStakesNew(height) - } else if height >= upgrades.UpgradeBlock2 { - c.recalculateStakesOld2(height) - } else { - c.recalculateStakesOld1(height) - } -} - -func (c *Candidates) recalculateStakesOld1(height uint64) { - coinsCache := newCoinsCache() - - for _, pubkey := range c.getOrderedCandidates() { - candidate := c.getFromMap(pubkey) - stakes := c.GetStakes(candidate.PubKey) - for _, stake := range stakes { - stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) - } - - // apply updates for existing stakes - for _, update := range candidate.updates { - stake := c.GetStakeOfAddress(candidate.PubKey, update.Owner, update.Coin) - if stake != nil { - stake.addValue(update.Value) - update.setValue(big.NewInt(0)) - stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) - } - } - - updates := candidate.GetFilteredUpdates() - for _, update := range updates { - update.setBipValue(c.calculateBipValue(update.Coin, update.Value, false, true, coinsCache)) - } - // Sort updates in descending order - sort.SliceStable(updates, func(i, j int) bool { - return updates[i].BipValue.Cmp(updates[j].BipValue) == 1 - }) - - for _, update := range updates { - if candidate.stakesCount < MaxDelegatorsPerCandidate { - candidate.SetStakeAtIndex(candidate.stakesCount, update, true) - candidate.stakesCount++ - stakes = c.GetStakes(candidate.PubKey) - } else { - // find and replace smallest stake - index := -1 - var smallestStake *big.Int - for i, stake := range stakes { - if stake == nil { - index = i - smallestStake = big.NewInt(0) - break - } - - if smallestStake == nil || smallestStake.Cmp(stake.BipValue) == 1 { - smallestStake = big.NewInt(0).Set(stake.BipValue) - index = i - } - } - - if index == -1 || smallestStake.Cmp(update.BipValue) == 1 { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: update.Owner, - Amount: update.Value.String(), - Coin: update.Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(update.Owner, update.Coin, update.Value) - c.bus.Checker().AddCoin(update.Coin, big.NewInt(0).Neg(update.Value)) - update.setValue(big.NewInt(0)) - continue - } - - if stakes[index] != nil { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: stakes[index].Owner, - Amount: stakes[index].Value.String(), - Coin: stakes[index].Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(stakes[index].Owner, stakes[index].Coin, stakes[index].Value) - c.bus.Checker().AddCoin(stakes[index].Coin, big.NewInt(0).Neg(stakes[index].Value)) - } - - candidate.SetStakeAtIndex(index, update, true) - stakes = c.GetStakes(candidate.PubKey) - } - } - - candidate.clearUpdates() - - totalBipValue := big.NewInt(0) - for _, stake := range c.GetStakes(candidate.PubKey) { - if stake == nil { - continue - } - totalBipValue.Add(totalBipValue, stake.BipValue) - } - - candidate.setTotalBipStake(totalBipValue) - candidate.updateStakesCount() - } + c.recalculateStakes(height) } -func (c *Candidates) recalculateStakesOld2(height uint64) { - coinsCache := newCoinsCache() - - for _, pubkey := range c.getOrderedCandidates() { - candidate := c.getFromMap(pubkey) - stakes := c.GetStakes(candidate.PubKey) - for _, stake := range stakes { - stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) - } - - // apply updates for existing stakes - candidate.FilterUpdates() - for _, update := range candidate.updates { - stake := c.GetStakeOfAddress(candidate.PubKey, update.Owner, update.Coin) - if stake != nil { - stake.addValue(update.Value) - update.setValue(big.NewInt(0)) - stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) - } - } - - candidate.FilterUpdates() - for _, update := range candidate.updates { - update.setBipValue(c.calculateBipValue(update.Coin, update.Value, false, true, coinsCache)) - } - - for _, update := range candidate.updates { - // find and replace smallest stake - index := -1 - var smallestStake *big.Int - - if len(stakes) == 0 { - index = 0 - smallestStake = big.NewInt(0) - } else if len(stakes) < MaxDelegatorsPerCandidate { - for i, stake := range stakes { - if stake == nil { - index = i - break - } - } - - if index == -1 { - index = len(stakes) - } - - smallestStake = big.NewInt(0) - } else { - for i, stake := range stakes { - if stake == nil { - index = i - smallestStake = big.NewInt(0) - break - } - - if smallestStake == nil || smallestStake.Cmp(stake.BipValue) == 1 { - smallestStake = big.NewInt(0).Set(stake.BipValue) - index = i - } - } - } - - if index == -1 || smallestStake.Cmp(update.BipValue) == 1 { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: update.Owner, - Amount: update.Value.String(), - Coin: update.Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(update.Owner, update.Coin, update.Value) - c.bus.Checker().AddCoin(update.Coin, big.NewInt(0).Neg(update.Value)) - update.setValue(big.NewInt(0)) - continue - } - - if len(stakes) > index && stakes[index] != nil { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: stakes[index].Owner, - Amount: stakes[index].Value.String(), - Coin: stakes[index].Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(stakes[index].Owner, stakes[index].Coin, stakes[index].Value) - c.bus.Checker().AddCoin(stakes[index].Coin, big.NewInt(0).Neg(stakes[index].Value)) - } - - candidate.SetStakeAtIndex(index, update, true) - stakes = c.GetStakes(candidate.PubKey) - } - - candidate.clearUpdates() - - totalBipValue := big.NewInt(0) - for _, stake := range c.GetStakes(candidate.PubKey) { - if stake == nil { - continue - } - totalBipValue.Add(totalBipValue, stake.BipValue) - } - - candidate.setTotalBipStake(totalBipValue) - candidate.updateStakesCount() - } -} - -func (c *Candidates) recalculateStakesNew(height uint64) { +func (c *Candidates) recalculateStakes(height uint64) { coinsCache := newCoinsCache() for _, pubkey := range c.getOrderedCandidates() { @@ -452,7 +355,7 @@ func (c *Candidates) recalculateStakesNew(height uint64) { } } - candidate.FilterUpdates() + candidate.filterUpdates() for _, update := range candidate.updates { update.setBipValue(c.calculateBipValue(update.Coin, update.Value, false, true, coinsCache)) } @@ -476,30 +379,16 @@ func (c *Candidates) recalculateStakesNew(height uint64) { } if smallestStake.Cmp(update.BipValue) == 1 { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: update.Owner, - Amount: update.Value.String(), - Coin: update.Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(update.Owner, update.Coin, update.Value) - c.bus.Checker().AddCoin(update.Coin, big.NewInt(0).Neg(update.Value)) + c.stakeKick(update.Owner, update.Value, update.Coin, candidate.PubKey, height) update.setValue(big.NewInt(0)) continue } if stakes[index] != nil { - c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ - Address: stakes[index].Owner, - Amount: stakes[index].Value.String(), - Coin: stakes[index].Coin, - ValidatorPubKey: candidate.PubKey, - }) - c.bus.Accounts().AddBalance(stakes[index].Owner, stakes[index].Coin, stakes[index].Value) - c.bus.Checker().AddCoin(stakes[index].Coin, big.NewInt(0).Neg(stakes[index].Value)) + c.stakeKick(stakes[index].Owner, stakes[index].Value, stakes[index].Coin, candidate.PubKey, height) } - candidate.SetStakeAtIndex(index, update, true) + candidate.setStakeAtIndex(index, update, true) } candidate.clearUpdates() @@ -516,15 +405,44 @@ func (c *Candidates) recalculateStakesNew(height uint64) { } } +func (c *Candidates) stakeKick(owner types.Address, value *big.Int, coin types.CoinID, pubKey types.Pubkey, height uint64) { + c.bus.WaitList().AddToWaitList(owner, pubKey, coin, value) + c.bus.Events().AddEvent(uint32(height), &eventsdb.StakeKickEvent{ + Address: owner, + Amount: value.String(), + Coin: uint64(coin), + ValidatorPubKey: pubKey, + }) + c.bus.Checker().AddCoin(coin, big.NewInt(0).Neg(value)) +} + +// Exists returns wherever a candidate with given public key exists func (c *Candidates) Exists(pubkey types.Pubkey) bool { c.lock.RLock() defer c.lock.RUnlock() - _, exists := c.list[pubkey] + return c.existPubKey(pubkey) +} +func (c *Candidates) existPubKey(pubKey types.Pubkey) bool { + _, exists := c.pubKeyIDs[pubKey] return exists } +// IsBlockedPubKey returns if given public key is blacklisted +func (c *Candidates) IsBlockedPubKey(pubkey types.Pubkey) bool { + return c.isBlocked(pubkey) +} + +func (c *Candidates) isBlocked(pubKey types.Pubkey) bool { + c.lock.Lock() + defer c.lock.Unlock() + + _, exists := c.blockList[pubKey] + return exists +} + +// Count returns current amount of candidates func (c *Candidates) Count() int { c.lock.RLock() defer c.lock.RUnlock() @@ -532,7 +450,8 @@ func (c *Candidates) Count() int { return len(c.list) } -func (c *Candidates) IsNewCandidateStakeSufficient(coin types.CoinSymbol, stake *big.Int, limit int) bool { +// IsNewCandidateStakeSufficient determines if given stake is sufficient to create new candidate +func (c *Candidates) IsNewCandidateStakeSufficient(coin types.CoinID, stake *big.Int, limit int) bool { c.lock.RLock() defer c.lock.RUnlock() @@ -556,11 +475,13 @@ func (c *Candidates) IsNewCandidateStakeSufficient(coin types.CoinSymbol, stake return false } +// GetCandidate returns candidate by a public key func (c *Candidates) GetCandidate(pubkey types.Pubkey) *Candidate { return c.getFromMap(pubkey) } -func (c *Candidates) IsDelegatorStakeSufficient(address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, amount *big.Int) bool { +// IsDelegatorStakeSufficient determines if given stake is sufficient to add it to a candidate +func (c *Candidates) IsDelegatorStakeSufficient(address types.Address, pubkey types.Pubkey, coin types.CoinID, amount *big.Int) bool { stakes := c.GetStakes(pubkey) if len(stakes) < MaxDelegatorsPerCandidate { return true @@ -576,40 +497,44 @@ func (c *Candidates) IsDelegatorStakeSufficient(address types.Address, pubkey ty return false } -func (c *Candidates) Delegate(address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int, bipValue *big.Int) { - stake := &Stake{ +// Delegate adds a stake to a candidate +func (c *Candidates) Delegate(address types.Address, pubkey types.Pubkey, coin types.CoinID, value *big.Int, bipValue *big.Int) { + candidate := c.GetCandidate(pubkey) + candidate.addUpdate(&stake{ Owner: address, Coin: coin, Value: big.NewInt(0).Set(value), BipValue: big.NewInt(0).Set(bipValue), - } - - candidate := c.GetCandidate(pubkey) - candidate.addUpdate(stake) + }) c.bus.Checker().AddCoin(coin, value) } -func (c *Candidates) Edit(pubkey types.Pubkey, rewardAddress types.Address, ownerAddress types.Address) { +// Edit edits a candidate +func (c *Candidates) Edit(pubkey types.Pubkey, rewardAddress types.Address, ownerAddress types.Address, controlAddress types.Address) { candidate := c.getFromMap(pubkey) candidate.setOwner(ownerAddress) candidate.setReward(rewardAddress) + candidate.setControl(controlAddress) } +// SetOnline sets candidate status to CandidateStatusOnline func (c *Candidates) SetOnline(pubkey types.Pubkey) { c.getFromMap(pubkey).setStatus(CandidateStatusOnline) } +// SetOffline sets candidate status to CandidateStatusOffline func (c *Candidates) SetOffline(pubkey types.Pubkey) { c.getFromMap(pubkey).setStatus(CandidateStatusOffline) } -func (c *Candidates) SubStake(address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int) { - stake := c.GetStakeOfAddress(pubkey, address, coin) - stake.subValue(value) +// SubStake subs given value from delegator's stake +func (c *Candidates) SubStake(address types.Address, pubkey types.Pubkey, coin types.CoinID, value *big.Int) { + c.GetStakeOfAddress(pubkey, address, coin).subValue(value) c.bus.Checker().AddCoin(coin, big.NewInt(0).Neg(value)) } +// GetCandidates returns a list of all candidates func (c *Candidates) GetCandidates() []*Candidate { var candidates []*Candidate for _, pubkey := range c.getOrderedCandidates() { @@ -619,11 +544,12 @@ func (c *Candidates) GetCandidates() []*Candidate { return candidates } +// GetTotalStake calculates and returns total stake of a candidate func (c *Candidates) GetTotalStake(pubkey types.Pubkey) *big.Int { candidate := c.getFromMap(pubkey) if candidate.totalBipStake == nil { path := []byte{mainPrefix} - path = append(path, pubkey[:]...) + path = append(path, candidate.idBytes()...) path = append(path, totalStakePrefix) _, enc := c.iavl.Get(path) if len(enc) == 0 { @@ -637,10 +563,11 @@ func (c *Candidates) GetTotalStake(pubkey types.Pubkey) *big.Int { return candidate.totalBipStake } -func (c *Candidates) GetStakes(pubkey types.Pubkey) []*Stake { +// GetStakes returns list of stakes of candidate with given public key +func (c *Candidates) GetStakes(pubkey types.Pubkey) []*stake { candidate := c.GetCandidate(pubkey) - var stakes []*Stake + var stakes []*stake for i := 0; i < MaxDelegatorsPerCandidate; i++ { stake := candidate.stakes[i] if stake == nil { @@ -652,11 +579,8 @@ func (c *Candidates) GetStakes(pubkey types.Pubkey) []*Stake { return stakes } -func (c *Candidates) StakesCount(pubkey types.Pubkey) int { - return c.GetCandidate(pubkey).stakesCount -} - -func (c *Candidates) GetStakeOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinSymbol) *Stake { +// GetStakeOfAddress returns stake of address in given candidate and in given coin +func (c *Candidates) GetStakeOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinID) *stake { candidate := c.GetCandidate(pubkey) for _, stake := range candidate.stakes { if stake == nil { @@ -671,7 +595,8 @@ func (c *Candidates) GetStakeOfAddress(pubkey types.Pubkey, address types.Addres return nil } -func (c *Candidates) GetStakeValueOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinSymbol) *big.Int { +// GetStakeValueOfAddress returns stake VALUE of address in given candidate and in given coin +func (c *Candidates) GetStakeValueOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinID) *big.Int { stake := c.GetStakeOfAddress(pubkey, address, coin) if stake == nil { return nil @@ -680,53 +605,121 @@ func (c *Candidates) GetStakeValueOfAddress(pubkey types.Pubkey, address types.A return stake.Value } +// GetCandidateOwner returns candidate's owner address func (c *Candidates) GetCandidateOwner(pubkey types.Pubkey) types.Address { return c.getFromMap(pubkey).OwnerAddress } +// GetCandidateControl returns candidate's control address +func (c *Candidates) GetCandidateControl(pubkey types.Pubkey) types.Address { + return c.getFromMap(pubkey).ControlAddress +} + +// LoadCandidates loads only list of candidates (for read) func (c *Candidates) LoadCandidates() { - if c.loaded { + if c.checkAndSetLoaded() { return } - c.loaded = true - path := []byte{mainPrefix} - _, enc := c.iavl.Get(path) - if len(enc) == 0 { - c.list = map[types.Pubkey]*Candidate{} + _ = c.loadCandidatesList() +} + +// LoadCandidatesDeliver loads full info about candidates (for edit) +func (c *Candidates) LoadCandidatesDeliver() { + if c.checkAndSetLoaded() { return } - var candidates []*Candidate - if err := rlp.DecodeBytes(enc, &candidates); err != nil { - panic(fmt.Sprintf("failed to decode candidates: %s", err)) + c.maxID = c.loadCandidatesList() + + _, blockListEnc := c.iavl.Get([]byte{blockListPrefix}) + if len(blockListEnc) != 0 { + var blockList []types.Pubkey + if err := rlp.DecodeBytes(blockListEnc, &blockList); err != nil { + panic(fmt.Sprintf("failed to decode candidates block list: %s", err)) + } + + blockListMap := map[types.Pubkey]struct{}{} + for _, pubkey := range blockList { + blockListMap[pubkey] = struct{}{} + } + c.setBlockList(blockListMap) } - c.list = map[types.Pubkey]*Candidate{} - for _, candidate := range candidates { - // load total stake - path = append([]byte{mainPrefix}, candidate.PubKey.Bytes()...) - path = append(path, totalStakePrefix) - _, enc = c.iavl.Get(path) - if len(enc) == 0 { - candidate.totalBipStake = big.NewInt(0) - } else { - candidate.totalBipStake = big.NewInt(0).SetBytes(enc) + _, valueMaxID := c.iavl.Get([]byte{maxIDPrefix}) + if len(valueMaxID) != 0 { + c.maxID = binary.LittleEndian.Uint32(valueMaxID) + } + +} + +func (c *Candidates) loadCandidatesList() (maxID uint32) { + _, pubIDenc := c.iavl.Get([]byte{pubKeyIDPrefix}) + if len(pubIDenc) != 0 { + var pubIDs []pubkeyID + if err := rlp.DecodeBytes(pubIDenc, &pubIDs); err != nil { + panic(fmt.Sprintf("failed to decode candidates: %s", err)) + } + + pubKeyIDs := map[types.Pubkey]uint32{} + for _, v := range pubIDs { + pubKeyIDs[v.PubKey] = v.ID + if v.ID > maxID { + maxID = v.ID + } + } + c.setPubKeyIDs(pubKeyIDs) + } + + path := []byte{mainPrefix} + _, enc := c.iavl.Get(path) + if len(enc) != 0 { + var candidates []*Candidate + if err := rlp.DecodeBytes(enc, &candidates); err != nil { + panic(fmt.Sprintf("failed to decode candidates: %s", err)) } - candidate.setTmAddress() - c.setToMap(candidate.PubKey, candidate) + for _, candidate := range candidates { + // load total stake + path = append([]byte{mainPrefix}, candidate.idBytes()...) + path = append(path, totalStakePrefix) + _, enc = c.iavl.Get(path) + if len(enc) == 0 { + candidate.totalBipStake = big.NewInt(0) + } else { + candidate.totalBipStake = big.NewInt(0).SetBytes(enc) + } + + candidate.setTmAddress() + c.setToMap(candidate.PubKey, candidate) + } } + + return maxID } +func (c *Candidates) checkAndSetLoaded() bool { + c.lock.RLock() + if c.loaded { + c.lock.RUnlock() + return true + } + c.lock.RUnlock() + c.lock.Lock() + c.loaded = true + c.lock.Unlock() + return false +} + +// LoadStakes loads all stakes of candidates func (c *Candidates) LoadStakes() { - for pubkey := range c.list { + for pubkey := range c.pubKeyIDs { c.LoadStakesOfCandidate(pubkey) } } -func (c *Candidates) calculateBipValue(coinSymbol types.CoinSymbol, amount *big.Int, includeSelf, includeUpdates bool, coinsCache *coinsCache) *big.Int { - if coinSymbol.IsBaseCoin() { +func (c *Candidates) calculateBipValue(coinID types.CoinID, amount *big.Int, includeSelf, includeUpdates bool, coinsCache *coinsCache) *big.Int { + if coinID.IsBaseCoin() { return big.NewInt(0).Set(amount) } @@ -734,47 +727,50 @@ func (c *Candidates) calculateBipValue(coinSymbol types.CoinSymbol, amount *big. return big.NewInt(0) } - totalAmount := big.NewInt(0) - if includeSelf { - totalAmount.Set(amount) + coin := c.bus.Coins().GetCoin(coinID) + + totalDelegatedBasecoin, totalDelegatedValue := big.NewInt(0), big.NewInt(0) + if coinsCache.Exists(coinID) { + totalDelegatedBasecoin, totalDelegatedValue = coinsCache.Get(coinID) } - var totalPower *big.Int + if includeSelf { + totalDelegatedValue.Add(totalDelegatedValue, amount) + } - if coinsCache.Exists(coinSymbol) { - totalPower, totalAmount = coinsCache.Get(coinSymbol) - } else { + if !coinsCache.Exists(coinID) { candidates := c.GetCandidates() for _, candidate := range candidates { for _, stake := range candidate.stakes { - if stake != nil && stake.Coin == coinSymbol { - totalAmount.Add(totalAmount, stake.Value) + if stake != nil && stake.Coin == coinID { + totalDelegatedValue.Add(totalDelegatedValue, stake.Value) } } if includeUpdates { for _, update := range candidate.updates { - if update.Coin == coinSymbol { - totalAmount.Add(totalAmount, update.Value) + if update.Coin == coinID { + totalDelegatedValue.Add(totalDelegatedValue, update.Value) } } } } - coin := c.bus.Coins().GetCoin(coinSymbol) - - totalPower = formula.CalculateSaleReturn(coin.Volume, coin.Reserve, coin.Crr, totalAmount) - coinsCache.Set(coinSymbol, totalPower, totalAmount) + nonLockedSupply := big.NewInt(0).Sub(coin.Volume, totalDelegatedValue) + totalDelegatedBasecoin = big.NewInt(0).Sub(coin.Reserve, formula.CalculateSaleReturn(coin.Volume, coin.Reserve, coin.Crr, nonLockedSupply)) + coinsCache.Set(coinID, totalDelegatedBasecoin, totalDelegatedValue) } - return big.NewInt(0).Div(big.NewInt(0).Mul(totalPower, amount), totalAmount) + return big.NewInt(0).Div(big.NewInt(0).Mul(totalDelegatedBasecoin, amount), totalDelegatedValue) } +// Punish punished a candidate with given tendermint-address +// 1. Subs 1% from each stake +// 2. Calculate and return new total stake func (c *Candidates) Punish(height uint64, address types.TmAddress) *big.Int { totalStake := big.NewInt(0) candidate := c.GetCandidateByTendermintAddress(address) - stakes := c.GetStakes(candidate.PubKey) for _, stake := range stakes { newValue := big.NewInt(0).Set(stake.Value) @@ -788,8 +784,8 @@ func (c *Candidates) Punish(height uint64, address types.TmAddress) *big.Int { coin := c.bus.Coins().GetCoin(stake.Coin) ret := formula.CalculateSaleReturn(coin.Volume, coin.Reserve, coin.Crr, slashed) - c.bus.Coins().SubCoinVolume(coin.Symbol, slashed) - c.bus.Coins().SubCoinReserve(coin.Symbol, ret) + c.bus.Coins().SubCoinVolume(coin.ID, slashed) + c.bus.Coins().SubCoinReserve(coin.ID, ret) c.bus.App().AddTotalSlashed(ret) c.bus.Checker().AddCoin(stake.Coin, big.NewInt(0).Neg(ret)) @@ -798,10 +794,10 @@ func (c *Candidates) Punish(height uint64, address types.TmAddress) *big.Int { c.bus.Checker().AddCoin(stake.Coin, big.NewInt(0).Neg(slashed)) } - c.bus.Events().AddEvent(uint32(height), eventsdb.SlashEvent{ + c.bus.Events().AddEvent(uint32(height), &eventsdb.SlashEvent{ Address: stake.Owner, Amount: slashed.String(), - Coin: stake.Coin, + Coin: uint64(stake.Coin), ValidatorPubKey: candidate.PubKey, }) @@ -812,14 +808,15 @@ func (c *Candidates) Punish(height uint64, address types.TmAddress) *big.Int { return totalStake } +// SetStakes Sets stakes and updates of a candidate. Used in Import. func (c *Candidates) SetStakes(pubkey types.Pubkey, stakes []types.Stake, updates []types.Stake) { candidate := c.GetCandidate(pubkey) candidate.stakesCount = len(stakes) for _, u := range updates { - candidate.addUpdate(&Stake{ + candidate.addUpdate(&stake{ Owner: u.Owner, - Coin: u.Coin, + Coin: types.CoinID(u.Coin), Value: helpers.StringToBigInt(u.Value), BipValue: helpers.StringToBigInt(u.BipValue), }) @@ -830,9 +827,9 @@ func (c *Candidates) SetStakes(pubkey types.Pubkey, stakes []types.Stake, update count = MaxDelegatorsPerCandidate for _, u := range stakes[1000:] { - candidate.addUpdate(&Stake{ + candidate.addUpdate(&stake{ Owner: u.Owner, - Coin: u.Coin, + Coin: types.CoinID(u.Coin), Value: helpers.StringToBigInt(u.Value), BipValue: helpers.StringToBigInt(u.BipValue), }) @@ -840,9 +837,9 @@ func (c *Candidates) SetStakes(pubkey types.Pubkey, stakes []types.Stake, update } for i, s := range stakes[:count] { - candidate.stakes[i] = &Stake{ + candidate.stakes[i] = &stake{ Owner: s.Owner, - Coin: s.Coin, + Coin: types.CoinID(s.Coin), Value: helpers.StringToBigInt(s.Value), BipValue: helpers.StringToBigInt(s.BipValue), markDirty: func(index int) { @@ -855,18 +852,20 @@ func (c *Candidates) SetStakes(pubkey types.Pubkey, stakes []types.Stake, update } } +// Export exports all data to the given state func (c *Candidates) Export(state *types.AppState) { - c.LoadCandidates() + c.LoadCandidatesDeliver() c.LoadStakes() candidates := c.GetCandidates() + state.Candidates = make([]types.Candidate, 0, len(candidates)) for _, candidate := range candidates { candidateStakes := c.GetStakes(candidate.PubKey) stakes := make([]types.Stake, len(candidateStakes)) for i, s := range candidateStakes { stakes[i] = types.Stake{ Owner: s.Owner, - Coin: s.Coin, + Coin: uint64(s.Coin), Value: s.Value.String(), BipValue: s.BipValue.String(), } @@ -876,24 +875,32 @@ func (c *Candidates) Export(state *types.AppState) { for i, u := range candidate.updates { updates[i] = types.Stake{ Owner: u.Owner, - Coin: u.Coin, + Coin: uint64(u.Coin), Value: u.Value.String(), BipValue: u.BipValue.String(), } } state.Candidates = append(state.Candidates, types.Candidate{ - RewardAddress: candidate.RewardAddress, - OwnerAddress: candidate.OwnerAddress, - TotalBipStake: candidate.GetTotalBipStake().String(), - PubKey: candidate.PubKey, - Commission: candidate.Commission, - Status: candidate.Status, - Updates: updates, - Stakes: stakes, + ID: uint64(candidate.ID), + RewardAddress: candidate.RewardAddress, + OwnerAddress: candidate.OwnerAddress, + ControlAddress: candidate.ControlAddress, + TotalBipStake: candidate.GetTotalBipStake().String(), + PubKey: candidate.PubKey, + Commission: uint64(candidate.Commission), + Status: uint64(candidate.Status), + Updates: updates, + Stakes: stakes, }) } + for pubkey := range c.blockList { + state.BlockListCandidates = append(state.BlockListCandidates, pubkey) + } + sort.SliceStable(state.BlockListCandidates, func(i, j int) bool { + return bytes.Compare(state.BlockListCandidates[i].Bytes(), state.BlockListCandidates[j].Bytes()) == 1 + }) } func (c *Candidates) getOrderedCandidates() []types.Pubkey { @@ -916,20 +923,42 @@ func (c *Candidates) getFromMap(pubkey types.Pubkey) *Candidate { c.lock.RLock() defer c.lock.RUnlock() - return c.list[pubkey] + return c.list[c.id(pubkey)] } func (c *Candidates) setToMap(pubkey types.Pubkey, model *Candidate) { + id := model.ID + if id == 0 { + id = c.getOrNewID(pubkey) + model.ID = id + } + + c.lock.Lock() + defer c.lock.Unlock() + + c.list[id] = model +} + +func (c *Candidates) setBlockList(blockList map[types.Pubkey]struct{}) { c.lock.Lock() defer c.lock.Unlock() - c.list[pubkey] = model + c.blockList = blockList } +func (c *Candidates) setPubKeyIDs(list map[types.Pubkey]uint32) { + c.lock.Lock() + defer c.lock.Unlock() + + c.pubKeyIDs = list +} + +// SetTotalStake sets candidate's total bip stake. Used in Import. func (c *Candidates) SetTotalStake(pubkey types.Pubkey, stake *big.Int) { c.GetCandidate(pubkey).setTotalBipStake(stake) } +// LoadStakesOfCandidate loads stakes of given candidate from disk func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { candidate := c.GetCandidate(pubkey) @@ -937,7 +966,7 @@ func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { stakesCount := 0 for index := 0; index < MaxDelegatorsPerCandidate; index++ { path := []byte{mainPrefix} - path = append(path, candidate.PubKey.Bytes()...) + path = append(path, candidate.idBytes()...) path = append(path, stakesPrefix) path = append(path, []byte(fmt.Sprintf("%d", index))...) _, enc := c.iavl.Get(path) @@ -946,12 +975,12 @@ func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { continue } - stake := &Stake{} + stake := &stake{} if err := rlp.DecodeBytes(enc, stake); err != nil { panic(fmt.Sprintf("failed to decode stake: %s", err)) } - candidate.SetStakeAtIndex(index, stake, false) + candidate.setStakeAtIndex(index, stake, false) stakesCount++ } @@ -960,13 +989,13 @@ func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { // load updates path := []byte{mainPrefix} - path = append(path, candidate.PubKey.Bytes()...) + path = append(path, candidate.idBytes()...) path = append(path, updatesPrefix) _, enc := c.iavl.Get(path) if len(enc) == 0 { candidate.updates = nil } else { - var updates []*Stake + var updates []*stake if err := rlp.DecodeBytes(enc, &updates); err != nil { panic(fmt.Sprintf("failed to decode updated: %s", err)) } @@ -983,7 +1012,7 @@ func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { } // load total stake - path = append([]byte{mainPrefix}, candidate.PubKey.Bytes()...) + path = append([]byte{mainPrefix}, candidate.idBytes()...) path = append(path, totalStakePrefix) _, enc = c.iavl.Get(path) if len(enc) == 0 { @@ -995,3 +1024,94 @@ func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { candidate.setTmAddress() c.setToMap(candidate.PubKey, candidate) } + +// ChangePubKey change public key of a candidate from old to new +func (c *Candidates) ChangePubKey(old types.Pubkey, new types.Pubkey) { + if c.isBlocked(new) { + panic("Candidate with such public key (" + new.String() + ") exists in block list") + } + + c.getFromMap(old).setPublicKey(new) + c.setBlockPubKey(old) + c.setPubKeyID(new, c.pubKeyIDs[old]) + delete(c.pubKeyIDs, old) + c.isChangedPublicKeys = true +} + +func (c *Candidates) getOrNewID(pubKey types.Pubkey) uint32 { + c.lock.RLock() + id := c.id(pubKey) + c.lock.RUnlock() + if id != 0 { + return id + } + + c.lock.Lock() + c.isDirty = true + c.maxID++ + c.lock.Unlock() + + id = c.maxID + c.setPubKeyID(pubKey, id) + return id +} + +func (c *Candidates) id(pubKey types.Pubkey) uint32 { + return c.pubKeyIDs[pubKey] +} + +// ID returns an id of candidate by it's public key +func (c *Candidates) ID(pubKey types.Pubkey) uint32 { + c.lock.RLock() + defer c.lock.RUnlock() + + return c.id(pubKey) +} + +// PubKey returns a public key of candidate by it's ID +func (c *Candidates) PubKey(id uint32) types.Pubkey { + c.lock.RLock() + defer c.lock.RUnlock() + + candidate, ok := c.list[id] + if !ok { + panic(fmt.Sprintf("candidate by ID %d not found", id)) + } + + return candidate.PubKey +} + +func (c *Candidates) setPubKeyID(pubkey types.Pubkey, id uint32) { + if id == 0 { + panic("public key of candidate cannot be equal 0") + } + + c.lock.Lock() + defer c.lock.Unlock() + + if c.maxID < id { + c.maxID = id + } + + c.pubKeyIDs[pubkey] = id + c.isDirty = true +} + +func (c *Candidates) setBlockPubKey(p types.Pubkey) { + c.lock.Lock() + defer c.lock.Unlock() + + c.blockList[p] = struct{}{} + c.isDirty = true +} + +// AddToBlockPubKey blacklists given publickey +func (c *Candidates) AddToBlockPubKey(p types.Pubkey) { + c.setBlockPubKey(p) +} + +func (c *Candidates) maxIDBytes() []byte { + bs := make([]byte, 4) + binary.LittleEndian.PutUint32(bs, c.maxID) + return bs +} diff --git a/core/state/candidates/coins_cache.go b/core/state/candidates/coins_cache.go new file mode 100644 index 000000000..55140ce43 --- /dev/null +++ b/core/state/candidates/coins_cache.go @@ -0,0 +1,46 @@ +package candidates + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" +) + +type coinsCache struct { + list map[types.CoinID]*coinsCacheItem +} + +func newCoinsCache() *coinsCache { + return &coinsCache{list: map[types.CoinID]*coinsCacheItem{}} +} + +type coinsCacheItem struct { + totalBasecoin *big.Int + totalAmount *big.Int +} + +func (c *coinsCache) Exists(id types.CoinID) bool { + if c == nil { + return false + } + + _, exists := c.list[id] + + return exists +} + +func (c *coinsCache) Get(id types.CoinID) (totalBasecoin *big.Int, totalAmount *big.Int) { + return big.NewInt(0).Set(c.list[id].totalBasecoin), big.NewInt(0).Set(c.list[id].totalAmount) +} + +func (c *coinsCache) Set(id types.CoinID, totalBasecoin *big.Int, totalAmount *big.Int) { + if c == nil { + return + } + + if c.list[id] == nil { + c.list[id] = &coinsCacheItem{} + } + + c.list[id].totalAmount = big.NewInt(0).Set(totalAmount) + c.list[id].totalBasecoin = big.NewInt(0).Set(totalBasecoin) +} diff --git a/core/state/candidates/model.go b/core/state/candidates/model.go index 6b6830b09..b32f5edcf 100644 --- a/core/state/candidates/model.go +++ b/core/state/candidates/model.go @@ -1,23 +1,32 @@ package candidates import ( + "encoding/binary" "github.com/MinterTeam/minter-go-node/core/types" "github.com/tendermint/tendermint/crypto/ed25519" "math/big" "sort" ) +type pubkeyID struct { + PubKey types.Pubkey + ID uint32 +} + +// Candidate represents candidate object which is stored on disk type Candidate struct { - PubKey types.Pubkey - RewardAddress types.Address - OwnerAddress types.Address - Commission uint - Status byte + PubKey types.Pubkey + RewardAddress types.Address + OwnerAddress types.Address + ControlAddress types.Address + Commission uint32 + Status byte + ID uint32 totalBipStake *big.Int stakesCount int - stakes [MaxDelegatorsPerCandidate]*Stake - updates []*Stake + stakes [MaxDelegatorsPerCandidate]*stake + updates []*stake tmAddress *types.TmAddress isDirty bool @@ -26,6 +35,12 @@ type Candidate struct { dirtyStakes [MaxDelegatorsPerCandidate]bool } +func (candidate *Candidate) idBytes() []byte { + bs := make([]byte, 4) + binary.LittleEndian.PutUint32(bs, candidate.ID) + return bs +} + func (candidate *Candidate) setStatus(status byte) { candidate.isDirty = true candidate.Status = status @@ -41,7 +56,18 @@ func (candidate *Candidate) setReward(address types.Address) { candidate.RewardAddress = address } -func (candidate *Candidate) addUpdate(stake *Stake) { +func (candidate *Candidate) setControl(address types.Address) { + candidate.isDirty = true + candidate.ControlAddress = address +} + +func (candidate *Candidate) setPublicKey(pubKey types.Pubkey) { + candidate.isDirty = true + candidate.PubKey = pubKey + candidate.setTmAddress() +} + +func (candidate *Candidate) addUpdate(stake *stake) { candidate.isUpdatesDirty = true stake.markDirty = func(i int) { candidate.isUpdatesDirty = true @@ -65,6 +91,7 @@ func (candidate *Candidate) setTotalBipStake(totalBipValue *big.Int) { candidate.totalBipStake.Set(totalBipValue) } +// GetTmAddress returns tendermint-address of a candidate func (candidate *Candidate) GetTmAddress() types.TmAddress { return *candidate.tmAddress } @@ -80,8 +107,9 @@ func (candidate *Candidate) setTmAddress() { candidate.tmAddress = &address } -func (candidate *Candidate) GetFilteredUpdates() []*Stake { - var updates []*Stake +// getFilteredUpdates returns updates which is > 0 in their value + merge similar updates +func (candidate *Candidate) getFilteredUpdates() []*stake { + var updates []*stake for _, update := range candidate.updates { // skip updates with 0 stakes if update.Value.Cmp(big.NewInt(0)) != 1 { @@ -92,7 +120,7 @@ func (candidate *Candidate) GetFilteredUpdates() []*Stake { merged := false for _, u := range updates { if u.Coin == update.Coin && u.Owner == update.Owner { - u.Value.Add(u.Value, update.Value) + u.Value = big.NewInt(0).Add(u.Value, update.Value) merged = true break } @@ -106,29 +134,14 @@ func (candidate *Candidate) GetFilteredUpdates() []*Stake { return updates } -func (candidate *Candidate) FilterUpdates() { - var updates []*Stake - for _, update := range candidate.updates { - // skip updates with 0 stakes - if update.Value.Cmp(big.NewInt(0)) != 1 { - continue - } - - // merge updates - merged := false - for _, u := range updates { - if u.Coin == update.Coin && u.Owner == update.Owner { - u.Value.Add(u.Value, update.Value) - merged = true - break - } - } - - if !merged { - updates = append(updates, update) - } +// filterUpdates filters candidate updates: remove 0-valued updates and merge similar ones +func (candidate *Candidate) filterUpdates() { + if len(candidate.updates) == 0 { + return } + updates := candidate.getFilteredUpdates() + sort.SliceStable(updates, func(i, j int) bool { return updates[i].BipValue.Cmp(updates[j].BipValue) == 1 }) @@ -137,21 +150,12 @@ func (candidate *Candidate) FilterUpdates() { candidate.isUpdatesDirty = true } -func (candidate *Candidate) updateStakesCount() { - count := 0 - for _, stake := range candidate.stakes { - if stake != nil { - count++ - } - } - candidate.stakesCount = count -} - +// GetTotalBipStake returns total stake value of a candidate func (candidate *Candidate) GetTotalBipStake() *big.Int { return big.NewInt(0).Set(candidate.totalBipStake) } -func (candidate *Candidate) SetStakeAtIndex(index int, stake *Stake, isDirty bool) { +func (candidate *Candidate) setStakeAtIndex(index int, stake *stake, isDirty bool) { stake.markDirty = func(i int) { candidate.dirtyStakes[i] = true } @@ -164,9 +168,9 @@ func (candidate *Candidate) SetStakeAtIndex(index int, stake *Stake, isDirty boo } } -type Stake struct { +type stake struct { Owner types.Address - Coin types.CoinSymbol + Coin types.CoinID Value *big.Int BipValue *big.Int @@ -174,78 +178,25 @@ type Stake struct { markDirty func(int) } -func (stake *Stake) addValue(value *big.Int) { +func (stake *stake) addValue(value *big.Int) { stake.markDirty(stake.index) - stake.Value.Add(stake.Value, value) + stake.Value = big.NewInt(0).Add(stake.Value, value) } -func (stake *Stake) subValue(value *big.Int) { +func (stake *stake) subValue(value *big.Int) { stake.markDirty(stake.index) - stake.Value.Sub(stake.Value, value) + stake.Value = big.NewInt(0).Sub(stake.Value, value) } -func (stake *Stake) setBipValue(value *big.Int) { +func (stake *stake) setBipValue(value *big.Int) { if stake.BipValue.Cmp(value) != 0 { stake.markDirty(stake.index) } - stake.BipValue.Set(value) + stake.BipValue = big.NewInt(0).Set(value) } -func (stake *Stake) setNewOwner(coin types.CoinSymbol, owner types.Address) { - stake.Coin = coin - stake.Owner = owner - stake.BipValue = big.NewInt(0) - stake.Value = big.NewInt(0) +func (stake *stake) setValue(ret *big.Int) { stake.markDirty(stake.index) -} - -func (stake *Stake) setValue(ret *big.Int) { - stake.markDirty(stake.index) - stake.Value.Set(ret) -} - -func (stake *Stake) setCoin(coin types.CoinSymbol) { - stake.markDirty(stake.index) - stake.Coin = coin -} - -type coinsCache struct { - list map[types.CoinSymbol]*coinsCacheItem -} - -func newCoinsCache() *coinsCache { - return &coinsCache{list: map[types.CoinSymbol]*coinsCacheItem{}} -} - -type coinsCacheItem struct { - totalPower *big.Int - totalAmount *big.Int -} - -func (c *coinsCache) Exists(symbol types.CoinSymbol) bool { - if c == nil { - return false - } - - _, exists := c.list[symbol] - - return exists -} - -func (c *coinsCache) Get(symbol types.CoinSymbol) (totalPower *big.Int, totalAmount *big.Int) { - return c.list[symbol].totalPower, c.list[symbol].totalAmount -} - -func (c *coinsCache) Set(symbol types.CoinSymbol, totalPower *big.Int, totalAmount *big.Int) { - if c == nil { - return - } - - if c.list[symbol] == nil { - c.list[symbol] = &coinsCacheItem{} - } - - c.list[symbol].totalAmount = totalAmount - c.list[symbol].totalPower = totalPower + stake.Value = big.NewInt(0).Set(ret) } diff --git a/core/state/candidates_test.go b/core/state/candidates_test.go index 2e9556652..bd6770686 100644 --- a/core/state/candidates_test.go +++ b/core/state/candidates_test.go @@ -3,23 +3,23 @@ package state import ( "crypto/rand" "encoding/binary" - eventsdb "github.com/MinterTeam/events-db" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/upgrades" + "github.com/MinterTeam/minter-go-node/helpers" "github.com/tendermint/tendermint/crypto/ed25519" db "github.com/tendermint/tm-db" "math/big" "testing" ) -const height = upgrades.UpgradeBlock3 +const height = 1 func TestSimpleDelegate(t *testing.T) { st := getState() address := types.Address{} - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() amount := big.NewInt(1) pubkey := createTestCandidate(st) @@ -38,13 +38,18 @@ func TestSimpleDelegate(t *testing.T) { if stake.BipValue.Cmp(amount) != 0 { t.Errorf("Bip value of stake of address %s should be %s, got %s", address.String(), amount.String(), stake.BipValue.String()) } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestDelegate(t *testing.T) { st := getState() address := types.Address{} - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() amount := big.NewInt(1) totalAmount := big.NewInt(0) pubkey := createTestCandidate(st) @@ -68,12 +73,55 @@ func TestDelegate(t *testing.T) { if stake.BipValue.Cmp(totalAmount) != 0 { t.Errorf("Bip value of stake of address %s should be %s, got %s", address.String(), amount.String(), stake.BipValue.String()) } + + err := checkState(st) + if err != nil { + t.Error(err) + } +} + +func TestCustomDelegate(t *testing.T) { + st := getState() + + volume := helpers.BipToPip(big.NewInt(1000000)) + reserve := helpers.BipToPip(big.NewInt(1000000)) + + coinID := st.App.GetNextCoinID() + st.Coins.Create(coinID, types.StrToCoinSymbol("TEST"), "TEST COIN", volume, 10, reserve, volume, nil) + st.Accounts.AddBalance([20]byte{1}, 1, helpers.BipToPip(big.NewInt(1000000-500000))) + st.App.SetCoinsCount(coinID.Uint32()) + + address := types.Address{} + amount := helpers.BipToPip(big.NewInt(500000)) + pubkey := createTestCandidate(st) + + st.Candidates.Delegate(address, pubkey, coinID, amount, big.NewInt(0)) + st.Candidates.RecalculateStakes(height) + + stake := st.Candidates.GetStakeOfAddress(pubkey, address, coinID) + if stake == nil { + t.Fatalf("Stake of address %s not found", address.String()) + } + + if stake.Value.Cmp(amount) != 0 { + t.Errorf("Stake of address %s should be %s, got %s", address.String(), amount.String(), stake.Value.String()) + } + + bipValue := big.NewInt(0).Mul(big.NewInt(9765625), big.NewInt(100000000000000)) + if stake.BipValue.Cmp(bipValue) != 0 { + t.Errorf("Bip value of stake of address %s should be %s, got %s", address.String(), bipValue.String(), stake.BipValue.String()) + } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestComplexDelegate(t *testing.T) { st := getState() - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() pubkey := createTestCandidate(st) for i := uint64(0); i < 2000; i++ { @@ -168,12 +216,17 @@ func TestComplexDelegate(t *testing.T) { t.Fatalf("Stake of address %s found, but should not be", addr.String()) } } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestStakeSufficiency(t *testing.T) { st := getState() - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() pubkey := createTestCandidate(st) for i := uint64(0); i < 1000; i++ { @@ -217,6 +270,11 @@ func TestStakeSufficiency(t *testing.T) { t.Fatalf("Stake of %s %s of address %s shold be sufficient", stake.String(), coin.String(), addr.String()) } } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestDoubleSignPenalty(t *testing.T) { @@ -224,7 +282,7 @@ func TestDoubleSignPenalty(t *testing.T) { pubkey := createTestCandidate(st) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() amount := big.NewInt(100) var addr types.Address binary.BigEndian.PutUint64(addr[:], 1) @@ -232,12 +290,16 @@ func TestDoubleSignPenalty(t *testing.T) { st.Candidates.RecalculateStakes(height) + st.FrozenFunds.AddFund(1, addr, pubkey, st.Candidates.ID(pubkey), coin, amount) + var pk ed25519.PubKeyEd25519 copy(pk[:], pubkey[:]) var tmAddr types.TmAddress copy(tmAddr[:], pk.Address().Bytes()) + st.Validators.PunishByzantineValidator(tmAddr) + st.FrozenFunds.PunishFrozenFundsWithID(1, 1+candidates.UnbondPeriod, st.Candidates.ID(pubkey)) st.Candidates.PunishByzantineCandidate(1, tmAddr) stake := st.Candidates.GetStakeValueOfAddress(pubkey, addr, coin) @@ -264,6 +326,11 @@ func TestDoubleSignPenalty(t *testing.T) { if !exists { t.Fatalf("Frozen fund not found") } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestAbsentPenalty(t *testing.T) { @@ -271,7 +338,7 @@ func TestAbsentPenalty(t *testing.T) { pubkey := createTestCandidate(st) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() amount := big.NewInt(100) var addr types.Address binary.BigEndian.PutUint64(addr[:], 1) @@ -294,6 +361,11 @@ func TestAbsentPenalty(t *testing.T) { if stake.Cmp(newValue) != 0 { t.Fatalf("Stake is not correct. Expected %s, got %s", newValue, stake.String()) } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestDoubleAbsentPenalty(t *testing.T) { @@ -301,11 +373,11 @@ func TestDoubleAbsentPenalty(t *testing.T) { pubkey := createTestCandidate(st) - coin := types.GetBaseCoin() - amount := big.NewInt(100) + coin := types.GetBaseCoinID() + amount := helpers.BipToPip(big.NewInt(10000)) var addr types.Address binary.BigEndian.PutUint64(addr[:], 1) - st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) + st.Candidates.Delegate(addr, pubkey, coin, amount, amount) st.Candidates.SetOnline(pubkey) st.Candidates.RecalculateStakes(height) @@ -330,12 +402,56 @@ func TestDoubleAbsentPenalty(t *testing.T) { if stake.Cmp(newValue) != 0 { t.Fatalf("Stake is not correct. Expected %s, got %s", newValue, stake.String()) } + + st.Candidates.SetOnline(pubkey) + st.Validators.SetNewValidators(st.Candidates.GetNewCandidates(1)) + err := checkState(st) + if err != nil { + t.Error(err) + } +} + +func TestZeroStakePenalty(t *testing.T) { + st := getState() + + pubkey := createTestCandidate(st) + + coin := types.GetBaseCoinID() + amount := big.NewInt(10000) + var addr types.Address + binary.BigEndian.PutUint64(addr[:], 1) + st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) + + st.Candidates.RecalculateStakes(height) + + st.Candidates.SubStake(addr, pubkey, coin, amount) + st.FrozenFunds.AddFund(518400, addr, pubkey, st.Candidates.ID(pubkey), coin, amount) + + var pk ed25519.PubKeyEd25519 + copy(pk[:], pubkey[:]) + + var tmAddr types.TmAddress + copy(tmAddr[:], pk.Address().Bytes()) + + st.Candidates.Punish(1, tmAddr) + + stake := st.Candidates.GetStakeValueOfAddress(pubkey, addr, coin) + newValue := big.NewInt(0) + + if stake.Cmp(newValue) != 0 { + t.Fatalf("Stake is not correct. Expected %s, got %s", newValue, stake.String()) + } + + err := checkState(st) + if err != nil { + t.Error(err) + } } func TestDelegationAfterUnbond(t *testing.T) { st := getState() - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() pubkey := createTestCandidate(st) for i := uint64(0); i < 1000; i++ { @@ -355,7 +471,9 @@ func TestDelegationAfterUnbond(t *testing.T) { st.Candidates.SubStake(addr, pubkey, coin, amount) st.Candidates.RecalculateStakes(height) - st.Candidates.Commit() + if err := st.Candidates.Commit(); err != nil { + panic(err) + } } // delegate @@ -387,6 +505,83 @@ func TestDelegationAfterUnbond(t *testing.T) { } } + err := checkState(st) + if err != nil { + t.Error(err) + } + +} + +func TestStakeKick(t *testing.T) { + st := getState() + + coin := types.GetBaseCoinID() + pubkey := createTestCandidate(st) + + for i := uint64(0); i < 1000; i++ { + amount := big.NewInt(int64(1000 - i)) + var addr types.Address + binary.BigEndian.PutUint64(addr[:], i) + st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) + } + + st.Candidates.RecalculateStakes(height) + + { + amount := big.NewInt(1001) + var addr types.Address + binary.BigEndian.PutUint64(addr[:], 1001) + st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) + } + + st.Candidates.RecalculateStakes(height) + + var addr types.Address + binary.BigEndian.PutUint64(addr[:], 999) + wl := st.Waitlist.Get(addr, pubkey, coin) + + if wl == nil { + t.Fatalf("Waitlist is empty") + } + + if wl.Value.Cmp(big.NewInt(1)) != 0 { + t.Fatalf("Waitlist is not correct") + } + + err := checkState(st) + if err != nil { + t.Error(err) + } +} + +func TestRecalculateStakes(t *testing.T) { + st := getState() + + st.Coins.Create(1, [10]byte{1}, "TestCoin", helpers.BipToPip(big.NewInt(100000)), 70, helpers.BipToPip(big.NewInt(10000)), nil, nil) + pubkey := createTestCandidate(st) + + st.Accounts.AddBalance([20]byte{1}, 1, helpers.BipToPip(big.NewInt(100000-1000))) + amount := helpers.BipToPip(big.NewInt(1000)) + st.Candidates.Delegate([20]byte{1}, pubkey, 1, amount, big.NewInt(0)) + + st.Candidates.RecalculateStakes(height) + err := st.Candidates.Commit() + if err != nil { + t.Fatal(err) + } + stake := st.Candidates.GetStakeOfAddress(pubkey, [20]byte{1}, 1) + + if stake.Value.String() != "1000000000000000000000" { + t.Errorf("stake value is %s", stake.Value.String()) + } + if stake.BipValue.String() != "13894954943731374342" { + t.Errorf("stake bip value is %s", stake.BipValue.String()) + } + + err = checkState(st) + if err != nil { + t.Error(err) + } } func getState() *State { @@ -399,12 +594,26 @@ func getState() *State { return s } +func checkState(cState *State) error { + if _, err := cState.Commit(); err != nil { + return err + } + + exportedState := cState.Export(height) + if err := exportedState.Verify(); err != nil { + return err + } + + return nil +} + func createTestCandidate(stateDB *State) types.Pubkey { address := types.Address{} pubkey := types.Pubkey{} _, _ = rand.Read(pubkey[:]) - stateDB.Candidates.Create(address, address, pubkey, 10) + stateDB.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1000))) + stateDB.Candidates.Create(address, address, address, pubkey, 10) return pubkey } diff --git a/core/state/checker/checker.go b/core/state/checker/checker.go index d4a953699..8dcd7e327 100644 --- a/core/state/checker/checker.go +++ b/core/state/checker/checker.go @@ -7,21 +7,21 @@ import ( ) type Checker struct { - delta map[types.CoinSymbol]*big.Int - volumeDelta map[types.CoinSymbol]*big.Int + delta map[types.CoinID]*big.Int + volumeDelta map[types.CoinID]*big.Int } func NewChecker(bus *bus.Bus) *Checker { checker := &Checker{ - delta: map[types.CoinSymbol]*big.Int{}, - volumeDelta: map[types.CoinSymbol]*big.Int{}, + delta: map[types.CoinID]*big.Int{}, + volumeDelta: map[types.CoinID]*big.Int{}, } bus.SetChecker(checker) return checker } -func (c *Checker) AddCoin(coin types.CoinSymbol, value *big.Int, msg ...string) { +func (c *Checker) AddCoin(coin types.CoinID, value *big.Int, msg ...string) { cValue, exists := c.delta[coin] if !exists { @@ -32,7 +32,7 @@ func (c *Checker) AddCoin(coin types.CoinSymbol, value *big.Int, msg ...string) cValue.Add(cValue, value) } -func (c *Checker) AddCoinVolume(coin types.CoinSymbol, value *big.Int) { +func (c *Checker) AddCoinVolume(coin types.CoinID, value *big.Int) { cValue, exists := c.volumeDelta[coin] if !exists { @@ -44,14 +44,14 @@ func (c *Checker) AddCoinVolume(coin types.CoinSymbol, value *big.Int) { } func (c *Checker) Reset() { - c.delta = map[types.CoinSymbol]*big.Int{} - c.volumeDelta = map[types.CoinSymbol]*big.Int{} + c.delta = map[types.CoinID]*big.Int{} + c.volumeDelta = map[types.CoinID]*big.Int{} } -func (c *Checker) Deltas() map[types.CoinSymbol]*big.Int { +func (c *Checker) Deltas() map[types.CoinID]*big.Int { return c.delta } -func (c *Checker) VolumeDeltas() map[types.CoinSymbol]*big.Int { +func (c *Checker) VolumeDeltas() map[types.CoinID]*big.Int { return c.volumeDelta } diff --git a/core/state/checks/checks.go b/core/state/checks/checks.go index c32dc939c..6c1843717 100644 --- a/core/state/checks/checks.go +++ b/core/state/checks/checks.go @@ -12,15 +12,20 @@ import ( const mainPrefix = byte('t') +type RChecks interface { + Export(state *types.AppState) + IsCheckUsed(check *check.Check) bool +} + type Checks struct { usedChecks map[types.Hash]struct{} - iavl tree.Tree + iavl tree.MTree lock sync.RWMutex } -func NewChecks(iavl tree.Tree) (*Checks, error) { +func NewChecks(iavl tree.MTree) (*Checks, error) { return &Checks{iavl: iavl, usedChecks: map[types.Hash]struct{}{}}, nil } diff --git a/core/state/coins/bus.go b/core/state/coins/bus.go index cd9dd2d56..7564262ed 100644 --- a/core/state/coins/bus.go +++ b/core/state/coins/bus.go @@ -14,25 +14,27 @@ func NewBus(coins *Coins) *Bus { return &Bus{coins: coins} } -func (b *Bus) GetCoin(symbol types.CoinSymbol) *bus.Coin { - coin := b.coins.GetCoin(symbol) +func (b *Bus) GetCoin(id types.CoinID) *bus.Coin { + coin := b.coins.GetCoin(id) if coin == nil { return nil } return &bus.Coin{ + ID: coin.id, Name: coin.Name(), Crr: coin.Crr(), Symbol: coin.Symbol(), Volume: coin.Volume(), Reserve: coin.Reserve(), + Version: coin.Version(), } } -func (b *Bus) SubCoinVolume(symbol types.CoinSymbol, amount *big.Int) { - b.coins.SubVolume(symbol, amount) +func (b *Bus) SubCoinVolume(id types.CoinID, amount *big.Int) { + b.coins.SubVolume(id, amount) } -func (b *Bus) SubCoinReserve(symbol types.CoinSymbol, amount *big.Int) { - b.coins.SubReserve(symbol, amount) +func (b *Bus) SubCoinReserve(id types.CoinID, amount *big.Int) { + b.coins.SubReserve(id, amount) } diff --git a/core/state/coins/coins.go b/core/state/coins/coins.go index 982d490ff..6bb4906d6 100644 --- a/core/state/coins/coins.go +++ b/core/state/coins/coins.go @@ -1,10 +1,10 @@ package coins import ( - "bytes" "fmt" "github.com/MinterTeam/minter-go-node/core/state/bus" "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" "math/big" @@ -13,22 +13,82 @@ import ( ) const ( - mainPrefix = byte('q') - infoPrefix = byte('i') + mainPrefix = byte('q') + infoPrefix = byte('i') + symbolPrefix = byte('s') + + BaseVersion types.CoinVersion = 0 ) +type RCoins interface { + Export(state *types.AppState) + Exists(id types.CoinID) bool + ExistsBySymbol(symbol types.CoinSymbol) bool + SubReserve(symbol types.CoinID, amount *big.Int) + GetCoin(id types.CoinID) *Model + GetCoinBySymbol(symbol types.CoinSymbol, version types.CoinVersion) *Model + GetSymbolInfo(symbol types.CoinSymbol) *SymbolInfo +} + +// Coins represents coins state in blockchain. +// +// When a coin is created with a CreateCoinTx transaction, such model is created: +// Model { +// id +// info +// symbolInfo +// +// Name +// Crr +// MaxSupply +// Version - is the version of the coin +// Symbol - is the base symbol of the coin +// } +// +// Also, SymbolInfo is created: +// SymbolInfo { +// OwnerAddress +// } +// +// It is a structure that retains the owner of the ticker (not the coin). +// coin.symbolInfo is saved in the symbolsInfoList map, in which the key is the symbol, +// and the value is the owner of the coin, and upon commit is written to the db by this key: +// mainPrefix + symbolPrefix + symbol + infoPrefix. +// +// Also, you need to save all coins for a particular ticker. That is, you need to associate +// the ticker with all the coins that refer to it. For this, there is a map symbolsList, +// in which the key is the ticker, and the value is an array of ids of coins that +// belong to this ticker (just with a different version). When you commit, this array is +// saved to db by this key: mainPrefix + symbolPrefix + symbol. +// +// The coin model is saved at: mainPrefix + id. +// +// When a coin is re-created with a RecreateCoinTx transaction, the state retrieves an array of +// coins that refer to this ticker (getBySymbol). Finds the current current version there, changes +// it to the new version. And the new coin is assigned version 0. The new coin is also added to symbolsList [ticker]. +// +// When changing the owner with a ChangeOwnerTx transaction, the state gets the current owner +// getSymbolInfo (ticker) and changes the owner there and saves it back. type Coins struct { - list map[types.CoinSymbol]*Model - dirty map[types.CoinSymbol]struct{} + list map[types.CoinID]*Model + dirty map[types.CoinID]struct{} + symbolsList map[types.CoinSymbol][]types.CoinID + symbolsInfoList map[types.CoinSymbol]*SymbolInfo bus *bus.Bus - iavl tree.Tree + iavl tree.MTree lock sync.RWMutex } -func NewCoins(stateBus *bus.Bus, iavl tree.Tree) (*Coins, error) { - coins := &Coins{bus: stateBus, iavl: iavl, list: map[types.CoinSymbol]*Model{}, dirty: map[types.CoinSymbol]struct{}{}} +func NewCoins(stateBus *bus.Bus, iavl tree.MTree) (*Coins, error) { + coins := &Coins{ + bus: stateBus, iavl: iavl, + list: map[types.CoinID]*Model{}, + dirty: map[types.CoinID]struct{}{}, + symbolsList: map[types.CoinSymbol][]types.CoinID{}, + symbolsInfoList: map[types.CoinSymbol]*SymbolInfo{}, + } coins.bus.SetCoins(NewBus(coins)) return coins, nil @@ -36,93 +96,149 @@ func NewCoins(stateBus *bus.Bus, iavl tree.Tree) (*Coins, error) { func (c *Coins) Commit() error { coins := c.getOrderedDirtyCoins() - for _, symbol := range coins { - coin := c.getFromMap(symbol) + for _, id := range coins { + coin := c.getFromMap(id) c.lock.Lock() - delete(c.dirty, symbol) + delete(c.dirty, id) c.lock.Unlock() + if coin.IsCreated() { + ids := c.getBySymbol(coin.Symbol()) + data, err := rlp.EncodeToBytes(ids) + if err != nil { + return fmt.Errorf("can't encode object at %d: %v", id, err) + } + + c.iavl.Set(getSymbolCoinsPath(coin.Symbol()), data) + coin.isCreated = false + } + if coin.IsDirty() { data, err := rlp.EncodeToBytes(coin) if err != nil { - return fmt.Errorf("can't encode object at %x: %v", symbol[:], err) + return fmt.Errorf("can't encode object at %d: %v", id, err) } - path := []byte{mainPrefix} - path = append(path, symbol[:]...) - c.iavl.Set(path, data) + c.iavl.Set(getCoinPath(id), data) coin.isDirty = false } if coin.IsInfoDirty() { data, err := rlp.EncodeToBytes(coin.info) if err != nil { - return fmt.Errorf("can't encode object at %x: %v", symbol[:], err) + return fmt.Errorf("can't encode object at %d: %v", id, err) } - path := []byte{mainPrefix} - path = append(path, symbol[:]...) - path = append(path, infoPrefix) - c.iavl.Set(path, data) + c.iavl.Set(getCoinInfoPath(id), data) coin.info.isDirty = false } + + if coin.IsSymbolInfoDirty() { + data, err := rlp.EncodeToBytes(coin.symbolInfo) + if err != nil { + return fmt.Errorf("can't encode object at %d: %v", id, err) + } + + c.iavl.Set(getSymbolInfoPath(coin.Symbol()), data) + coin.symbolInfo.isDirty = false + } } return nil } -func (c *Coins) GetCoin(symbol types.CoinSymbol) *Model { - return c.get(symbol) +func (c *Coins) GetCoin(id types.CoinID) *Model { + return c.get(id) +} + +func (c *Coins) GetSymbolInfo(symbol types.CoinSymbol) *SymbolInfo { + return c.getSymbolInfo(symbol) +} + +func (c *Coins) Exists(id types.CoinID) bool { + if id.IsBaseCoin() { + return true + } + + return c.get(id) != nil } -func (c *Coins) Exists(symbol types.CoinSymbol) bool { +func (c *Coins) ExistsBySymbol(symbol types.CoinSymbol) bool { if symbol.IsBaseCoin() { return true } - return c.get(symbol) != nil + return c.getBySymbol(symbol) != nil } -func (c *Coins) SubVolume(symbol types.CoinSymbol, amount *big.Int) { +func (c *Coins) GetCoinBySymbol(symbol types.CoinSymbol, version types.CoinVersion) *Model { if symbol.IsBaseCoin() { + return c.get(types.GetBaseCoinID()) + } + + coins := c.getBySymbol(symbol) + if len(coins) == 0 { + return nil + } + + for _, coinID := range coins { + coin := c.get(coinID) + if coin.Version() == version { + return coin + } + } + + return nil +} + +func (c *Coins) SubVolume(id types.CoinID, amount *big.Int) { + if id.IsBaseCoin() { return } - c.get(symbol).SubVolume(amount) - c.bus.Checker().AddCoinVolume(symbol, big.NewInt(0).Neg(amount)) + + c.get(id).SubVolume(amount) + c.bus.Checker().AddCoinVolume(id, big.NewInt(0).Neg(amount)) } -func (c *Coins) AddVolume(symbol types.CoinSymbol, amount *big.Int) { - if symbol.IsBaseCoin() { +func (c *Coins) AddVolume(id types.CoinID, amount *big.Int) { + if id.IsBaseCoin() { return } - c.get(symbol).AddVolume(amount) - c.bus.Checker().AddCoinVolume(symbol, amount) + + c.get(id).AddVolume(amount) + c.bus.Checker().AddCoinVolume(id, amount) } -func (c *Coins) SubReserve(symbol types.CoinSymbol, amount *big.Int) { - if symbol.IsBaseCoin() { +func (c *Coins) SubReserve(id types.CoinID, amount *big.Int) { + if id.IsBaseCoin() { return } - c.get(symbol).SubReserve(amount) - c.bus.Checker().AddCoin(types.GetBaseCoin(), big.NewInt(0).Neg(amount)) + + c.get(id).SubReserve(amount) + c.bus.Checker().AddCoin(types.GetBaseCoinID(), big.NewInt(0).Neg(amount)) } -func (c *Coins) AddReserve(symbol types.CoinSymbol, amount *big.Int) { - if symbol.IsBaseCoin() { +func (c *Coins) AddReserve(id types.CoinID, amount *big.Int) { + if id.IsBaseCoin() { return } - c.get(symbol).AddReserve(amount) - c.bus.Checker().AddCoin(types.GetBaseCoin(), amount) + + c.get(id).AddReserve(amount) + c.bus.Checker().AddCoin(types.GetBaseCoinID(), amount) } -func (c *Coins) Create(symbol types.CoinSymbol, name string, volume *big.Int, crr uint, reserve *big.Int, maxSupply *big.Int) { +func (c *Coins) Create(id types.CoinID, symbol types.CoinSymbol, name string, + volume *big.Int, crr uint32, reserve *big.Int, maxSupply *big.Int, owner *types.Address, +) { coin := &Model{ CName: name, CCrr: crr, CMaxSupply: maxSupply, - symbol: symbol, + CSymbol: symbol, + id: id, markDirty: c.markDirty, isDirty: true, + isCreated: true, info: &Info{ Volume: big.NewInt(0), Reserve: big.NewInt(0), @@ -130,107 +246,271 @@ func (c *Coins) Create(symbol types.CoinSymbol, name string, volume *big.Int, cr }, } - c.setToMap(coin.symbol, coin) + if owner != nil { + coin.symbolInfo = &SymbolInfo{ + COwnerAddress: owner, + isDirty: true, + } + + c.setSymbolInfoToMap(coin.symbolInfo, coin.Symbol()) + } + + ids := c.getBySymbol(coin.Symbol()) + ids = append(ids, coin.ID()) + + c.setSymbolToMap(ids, coin.Symbol()) + c.setToMap(coin.ID(), coin) coin.SetReserve(reserve) coin.SetVolume(volume) - c.markDirty(symbol) - c.bus.Checker().AddCoin(types.GetBaseCoin(), reserve) - c.bus.Checker().AddCoinVolume(symbol, volume) + c.markDirty(coin.id) + + c.bus.Checker().AddCoin(types.GetBaseCoinID(), reserve) + c.bus.Checker().AddCoinVolume(coin.id, volume) +} + +func (c *Coins) Recreate(newID types.CoinID, name string, symbol types.CoinSymbol, + volume *big.Int, crr uint32, reserve *big.Int, maxSupply *big.Int, +) { + recreateCoin := c.GetCoinBySymbol(symbol, BaseVersion) + if recreateCoin == nil { + panic("coin to recreate does not exists") + } + + // update version for recreating coin + symbolCoins := c.getBySymbol(symbol) + + lastVersion := uint16(0) + for _, id := range symbolCoins { + coin := c.get(id) + if coin.Version() > lastVersion { + lastVersion = coin.Version() + } + } + + recreateCoin.CVersion = lastVersion + 1 + recreateCoin.isDirty = true + + c.setToMap(recreateCoin.id, recreateCoin) + c.markDirty(recreateCoin.id) + + c.Create(newID, recreateCoin.Symbol(), name, volume, crr, reserve, maxSupply, nil) +} + +func (c *Coins) ChangeOwner(symbol types.CoinSymbol, owner types.Address) { + info := c.getSymbolInfo(symbol) + info.setOwnerAddress(owner) + + coin := c.GetCoinBySymbol(symbol, BaseVersion) + coin.symbolInfo = info + + c.setSymbolInfoToMap(coin.symbolInfo, coin.Symbol()) + c.setToMap(coin.ID(), coin) + c.markDirty(coin.ID()) } -func (c *Coins) get(symbol types.CoinSymbol) *Model { - if coin := c.getFromMap(symbol); coin != nil { +func (c *Coins) get(id types.CoinID) *Model { + if id.IsBaseCoin() { + return &Model{ + id: types.GetBaseCoinID(), + CSymbol: types.GetBaseCoin(), + CMaxSupply: helpers.BipToPip(big.NewInt(10000000000)), + info: &Info{ + Volume: big.NewInt(0), + Reserve: big.NewInt(0), + }, + } + } + + if coin := c.getFromMap(id); coin != nil { return coin } - path := []byte{mainPrefix} - path = append(path, symbol[:]...) - _, enc := c.iavl.Get(path) + _, enc := c.iavl.Get(getCoinPath(id)) if len(enc) == 0 { return nil } coin := &Model{} if err := rlp.DecodeBytes(enc, coin); err != nil { - panic(fmt.Sprintf("failed to decode coin at %s: %s", symbol.String(), err)) + panic(fmt.Sprintf("failed to decode coin at %d: %s", id, err)) } - coin.symbol = symbol + coin.id = id coin.markDirty = c.markDirty // load info - path = []byte{mainPrefix} - path = append(path, symbol[:]...) - path = append(path, infoPrefix) - _, enc = c.iavl.Get(path) + _, enc = c.iavl.Get(getCoinInfoPath(id)) if len(enc) != 0 { var info Info if err := rlp.DecodeBytes(enc, &info); err != nil { - panic(fmt.Sprintf("failed to decode coin info %s: %s", symbol.String(), err)) + panic(fmt.Sprintf("failed to decode coin info %d: %s", id, err)) } coin.info = &info } - c.setToMap(symbol, coin) + c.setToMap(id, coin) return coin } -func (c *Coins) markDirty(symbol types.CoinSymbol) { - c.dirty[symbol] = struct{}{} +func (c *Coins) getSymbolInfo(symbol types.CoinSymbol) *SymbolInfo { + if info, ok := c.getSymbolInfoFromMap(symbol); ok { + return info + } + + info := &SymbolInfo{} + + _, enc := c.iavl.Get(getSymbolInfoPath(symbol)) + if len(enc) == 0 { + return nil + } + + if err := rlp.DecodeBytes(enc, info); err != nil { + panic(fmt.Sprintf("failed to decode coin symbol %s: %s", symbol.String(), err)) + } + + c.setSymbolInfoToMap(info, symbol) + + return info } -func (c *Coins) getOrderedDirtyCoins() []types.CoinSymbol { - keys := make([]types.CoinSymbol, 0, len(c.dirty)) +func (c *Coins) getBySymbol(symbol types.CoinSymbol) []types.CoinID { + if coins, ok := c.getSymbolFromMap(symbol); ok { + return coins + } + + var coins []types.CoinID + + _, enc := c.iavl.Get(getSymbolCoinsPath(symbol)) + if len(enc) == 0 { + return coins + } + + if err := rlp.DecodeBytes(enc, &coins); err != nil { + panic(fmt.Sprintf("failed to decode coins by symbol %s: %s", symbol, err)) + } + + c.setSymbolToMap(coins, symbol) + + return coins +} + +func (c *Coins) markDirty(id types.CoinID) { + c.dirty[id] = struct{}{} +} + +func (c *Coins) getOrderedDirtyCoins() []types.CoinID { + keys := make([]types.CoinID, 0, len(c.dirty)) for k := range c.dirty { keys = append(keys, k) } sort.SliceStable(keys, func(i, j int) bool { - return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + return keys[i] > keys[j] }) return keys } func (c *Coins) Export(state *types.AppState) { - // todo: iterate range? c.iavl.Iterate(func(key []byte, value []byte) bool { if key[0] == mainPrefix { - if len(key[1:]) > types.CoinSymbolLength { + if len(key) > 5 { return false } - coinSymbol := types.StrToCoinSymbol(string(key[1:])) - coin := c.GetCoin(coinSymbol) + coinID := types.BytesToCoinID(key[1:]) + coin := c.get(coinID) + + owner := &types.Address{} + info := c.getSymbolInfo(coin.Symbol()) + if info != nil { + owner = info.OwnerAddress() + } state.Coins = append(state.Coins, types.Coin{ - Name: coin.Name(), - Symbol: coin.Symbol(), - Volume: coin.Volume().String(), - Crr: coin.Crr(), - Reserve: coin.Reserve().String(), - MaxSupply: coin.MaxSupply().String(), + ID: uint64(coin.ID()), + Name: coin.Name(), + Symbol: coin.Symbol(), + Volume: coin.Volume().String(), + Crr: uint64(coin.Crr()), + Reserve: coin.Reserve().String(), + MaxSupply: coin.MaxSupply().String(), + Version: uint64(coin.Version()), + OwnerAddress: owner, }) } return false }) + + sort.Slice(state.Coins[:], func(i, j int) bool { + return helpers.StringToBigInt(state.Coins[i].Reserve).Cmp(helpers.StringToBigInt(state.Coins[j].Reserve)) == 1 + }) } -func (c *Coins) getFromMap(symbol types.CoinSymbol) *Model { +func (c *Coins) getFromMap(id types.CoinID) *Model { c.lock.RLock() defer c.lock.RUnlock() - return c.list[symbol] + return c.list[id] } -func (c *Coins) setToMap(symbol types.CoinSymbol, model *Model) { +func (c *Coins) setToMap(id types.CoinID, model *Model) { c.lock.Lock() defer c.lock.Unlock() - c.list[symbol] = model + c.list[id] = model +} + +func (c *Coins) getSymbolInfoFromMap(symbol types.CoinSymbol) (*SymbolInfo, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + info, ok := c.symbolsInfoList[symbol] + return info, ok +} + +func (c *Coins) setSymbolInfoToMap(info *SymbolInfo, symbol types.CoinSymbol) { + c.lock.Lock() + defer c.lock.Unlock() + + c.symbolsInfoList[symbol] = info +} + +func (c *Coins) getSymbolFromMap(symbol types.CoinSymbol) ([]types.CoinID, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + + coins, ok := c.symbolsList[symbol] + return coins, ok +} + +func (c *Coins) setSymbolToMap(coins []types.CoinID, symbol types.CoinSymbol) { + c.lock.Lock() + defer c.lock.Unlock() + + c.symbolsList[symbol] = coins +} + +func getSymbolCoinsPath(symbol types.CoinSymbol) []byte { + path := append([]byte{mainPrefix}, []byte{symbolPrefix}...) + return append(path, symbol.Bytes()...) +} + +func getSymbolInfoPath(symbol types.CoinSymbol) []byte { + path := append([]byte{mainPrefix}, []byte{symbolPrefix}...) + path = append(path, symbol.Bytes()...) + return append(path, []byte{infoPrefix}...) +} + +func getCoinPath(id types.CoinID) []byte { + return append([]byte{mainPrefix}, id.Bytes()...) +} + +func getCoinInfoPath(id types.CoinID) []byte { + return append(getCoinPath(id), infoPrefix) } diff --git a/core/state/coins/model.go b/core/state/coins/model.go index 262fc1146..c38683a40 100644 --- a/core/state/coins/model.go +++ b/core/state/coins/model.go @@ -11,13 +11,19 @@ var minCoinReserve = helpers.BipToPip(big.NewInt(10000)) type Model struct { CName string - CCrr uint + CCrr uint32 CMaxSupply *big.Int + CVersion types.CoinVersion + CSymbol types.CoinSymbol + + id types.CoinID + info *Info + symbolInfo *SymbolInfo + + markDirty func(symbol types.CoinID) - symbol types.CoinSymbol - info *Info - markDirty func(symbol types.CoinSymbol) isDirty bool + isCreated bool } func (m Model) Name() string { @@ -25,10 +31,14 @@ func (m Model) Name() string { } func (m Model) Symbol() types.CoinSymbol { - return m.symbol + return m.CSymbol +} + +func (m Model) ID() types.CoinID { + return m.id } -func (m Model) Crr() uint { +func (m Model) Crr() uint32 { return m.CCrr } @@ -40,39 +50,43 @@ func (m Model) Reserve() *big.Int { return big.NewInt(0).Set(m.info.Reserve) } +func (m Model) Version() uint16 { + return m.CVersion +} + func (m *Model) SubVolume(amount *big.Int) { m.info.Volume.Sub(m.info.Volume, amount) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } func (m *Model) AddVolume(amount *big.Int) { m.info.Volume.Add(m.info.Volume, amount) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } func (m *Model) SubReserve(amount *big.Int) { m.info.Reserve.Sub(m.info.Reserve, amount) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } func (m *Model) AddReserve(amount *big.Int) { m.info.Reserve.Add(m.info.Reserve, amount) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } func (m *Model) SetReserve(reserve *big.Int) { m.info.Reserve.Set(reserve) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } func (m *Model) SetVolume(volume *big.Int) { m.info.Volume.Set(volume) - m.markDirty(m.symbol) + m.markDirty(m.id) m.info.isDirty = true } @@ -81,7 +95,7 @@ func (m *Model) CheckReserveUnderflow(delta *big.Int) error { if total.Cmp(minCoinReserve) == -1 { min := big.NewInt(0).Add(minCoinReserve, delta) - return fmt.Errorf("coin %s reserve is too small (%s, required at least %s)", m.symbol.String(), m.Reserve().String(), min.String()) + return fmt.Errorf("coin %s reserve is too small (%s, required at least %s)", m.CSymbol.String(), m.Reserve().String(), min.String()) } return nil @@ -91,17 +105,48 @@ func (m Model) IsInfoDirty() bool { return m.info.isDirty } +func (m Model) IsSymbolInfoDirty() bool { + return m.symbolInfo != nil && m.symbolInfo.isDirty +} + func (m Model) IsDirty() bool { return m.isDirty } +func (m Model) IsCreated() bool { + return m.isCreated +} + func (m Model) MaxSupply() *big.Int { return m.CMaxSupply } +func (m Model) GetFullSymbol() string { + if m.Version() == 0 { + return m.Symbol().String() + } + + return fmt.Sprintf("%s-%d", m.Symbol(), m.Version()) +} + type Info struct { Volume *big.Int Reserve *big.Int isDirty bool } + +type SymbolInfo struct { + COwnerAddress *types.Address + + isDirty bool +} + +func (i *SymbolInfo) setOwnerAddress(address types.Address) { + i.COwnerAddress = &address + i.isDirty = true +} + +func (i SymbolInfo) OwnerAddress() *types.Address { + return i.COwnerAddress +} diff --git a/core/state/frozenfunds/bus.go b/core/state/frozenfunds/bus.go index 80f8fe93f..e7bffbd05 100644 --- a/core/state/frozenfunds/bus.go +++ b/core/state/frozenfunds/bus.go @@ -9,8 +9,8 @@ type Bus struct { frozenfunds *FrozenFunds } -func (b *Bus) AddFrozenFund(height uint64, address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int) { - b.frozenfunds.AddFund(height, address, pubkey, coin, value) +func (b *Bus) AddFrozenFund(height uint64, address types.Address, pubkey types.Pubkey, candidateID uint32, coin types.CoinID, value *big.Int) { + b.frozenfunds.AddFund(height, address, pubkey, candidateID, coin, value) } func NewBus(frozenfunds *FrozenFunds) *Bus { diff --git a/core/state/frozenfunds/frozen_funds.go b/core/state/frozenfunds/frozen_funds.go index 81b421f09..540035bb4 100644 --- a/core/state/frozenfunds/frozen_funds.go +++ b/core/state/frozenfunds/frozen_funds.go @@ -3,14 +3,13 @@ package frozenfunds import ( "encoding/binary" "fmt" - eventsdb "github.com/MinterTeam/events-db" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/state/bus" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" - "github.com/tendermint/tendermint/crypto/ed25519" "math/big" "sort" "sync" @@ -18,17 +17,22 @@ import ( const mainPrefix = byte('f') +type RFrozenFunds interface { + Export(state *types.AppState, height uint64) + GetFrozenFunds(height uint64) *Model +} + type FrozenFunds struct { list map[uint64]*Model dirty map[uint64]interface{} bus *bus.Bus - iavl tree.Tree + iavl tree.MTree lock sync.RWMutex } -func NewFrozenFunds(stateBus *bus.Bus, iavl tree.Tree) (*FrozenFunds, error) { +func NewFrozenFunds(stateBus *bus.Bus, iavl tree.MTree) (*FrozenFunds, error) { frozenfunds := &FrozenFunds{bus: stateBus, iavl: iavl, list: map[uint64]*Model{}, dirty: map[uint64]interface{}{}} frozenfunds.bus.SetFrozenFunds(NewBus(frozenfunds)) @@ -42,15 +46,25 @@ func (f *FrozenFunds) Commit() error { f.lock.Lock() delete(f.dirty, height) + delete(f.list, height) f.lock.Unlock() - data, err := rlp.EncodeToBytes(ff) - if err != nil { - return fmt.Errorf("can't encode object at %d: %v", height, err) - } - path := getPath(height) - f.iavl.Set(path, data) + + if ff.deleted { + f.lock.Lock() + delete(f.list, height) + f.lock.Unlock() + + f.iavl.Remove(path) + } else { + data, err := rlp.EncodeToBytes(ff) + if err != nil { + return fmt.Errorf("can't encode object at %d: %v", height, err) + } + + f.iavl.Set(path, data) + } } return nil @@ -60,7 +74,7 @@ func (f *FrozenFunds) GetFrozenFunds(height uint64) *Model { return f.get(height) } -func (f *FrozenFunds) PunishFrozenFundsWithAddress(fromHeight uint64, toHeight uint64, tmAddress types.TmAddress) { +func (f *FrozenFunds) PunishFrozenFundsWithID(fromHeight uint64, toHeight uint64, candidateID uint32) { for cBlock := fromHeight; cBlock <= toHeight; cBlock++ { ff := f.get(cBlock) if ff == nil { @@ -69,13 +83,7 @@ func (f *FrozenFunds) PunishFrozenFundsWithAddress(fromHeight uint64, toHeight u newList := make([]Item, len(ff.List)) for i, item := range ff.List { - var pubkey ed25519.PubKeyEd25519 - copy(pubkey[:], item.CandidateKey[:]) - - var address [20]byte - copy(address[:], pubkey.Address().Bytes()) - - if tmAddress == address { + if item.CandidateID == candidateID { newValue := big.NewInt(0).Set(item.Value) newValue.Mul(newValue, big.NewInt(95)) newValue.Div(newValue, big.NewInt(100)) @@ -95,10 +103,10 @@ func (f *FrozenFunds) PunishFrozenFundsWithAddress(fromHeight uint64, toHeight u f.bus.Checker().AddCoin(item.Coin, slashed) - f.bus.Events().AddEvent(uint32(fromHeight), eventsdb.SlashEvent{ + f.bus.Events().AddEvent(uint32(fromHeight), &eventsdb.SlashEvent{ Address: item.Address, Amount: slashed.String(), - Coin: item.Coin, + Coin: uint64(item.Coin), ValidatorPubKey: *item.CandidateKey, }) @@ -167,8 +175,8 @@ func (f *FrozenFunds) getOrderedDirty() []uint64 { return keys } -func (f *FrozenFunds) AddFund(height uint64, address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int) { - f.GetOrNew(height).addFund(address, pubkey, coin, value) +func (f *FrozenFunds) AddFund(height uint64, address types.Address, pubkey types.Pubkey, candidateId uint32, coin types.CoinID, value *big.Int) { + f.GetOrNew(height).addFund(address, pubkey, candidateId, coin, value) f.bus.Checker().AddCoin(coin, value) } @@ -197,7 +205,8 @@ func (f *FrozenFunds) Export(state *types.AppState, height uint64) { Height: i, Address: frozenFund.Address, CandidateKey: frozenFund.CandidateKey, - Coin: frozenFund.Coin, + CandidateID: uint64(frozenFund.CandidateID), + Coin: uint64(frozenFund.Coin), Value: frozenFund.Value.String(), }) } diff --git a/core/state/frozenfunds/frozen_funds_test.go b/core/state/frozenfunds/frozen_funds_test.go new file mode 100644 index 000000000..803f92481 --- /dev/null +++ b/core/state/frozenfunds/frozen_funds_test.go @@ -0,0 +1,119 @@ +package frozenfunds + +import ( + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/checker" + "github.com/MinterTeam/minter-go-node/core/state/coins" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/tree" + db "github.com/tendermint/tm-db" + "math/big" + "testing" +) + +func TestFrozenFundsToAddModel(t *testing.T) { + b := bus.NewBus() + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + + ff, err := NewFrozenFunds(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + b.SetChecker(checker.NewChecker(b)) + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + b.SetCoins(coins.NewBus(coinsState)) + + height, addr, pubkey, coin, val := uint64(1), types.Address{0}, types.Pubkey{0}, types.GetBaseCoinID(), big.NewInt(1e18) + + ff.AddFund(height, addr, pubkey, 1, coin, val) + if err := ff.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + funds := ff.GetFrozenFunds(height) + if funds == nil { + t.Fatal("Funds not found") + } + + if len(funds.List) != 1 { + t.Fatal("Incorrect amount of funds") + } + + if funds.Height() != height { + t.Fatal("Invalid funds data") + } + + f := funds.List[0] + if !pubkey.Equals(*f.CandidateKey) || f.Value.Cmp(val) != 0 || f.Address.Compare(addr) != 0 || f.Coin != coin { + t.Fatal("Invalid funds data") + } +} + +func TestFrozenFundsToDeleteModel(t *testing.T) { + b := bus.NewBus() + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + ff, err := NewFrozenFunds(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + b.SetChecker(checker.NewChecker(b)) + coinsState, err := coins.NewCoins(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + b.SetCoins(coins.NewBus(coinsState)) + + height, addr, pubkey, coin, val := uint64(1), types.Address{0}, types.Pubkey{0}, types.GetBaseCoinID(), big.NewInt(1e18) + + ff.AddFund(height, addr, pubkey, 1, coin, val) + if err := ff.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if funds := ff.GetFrozenFunds(height); funds == nil { + t.Fatal("Funds not found") + } + + ff.Delete(height) + + if err := ff.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if funds := ff.GetFrozenFunds(height); funds != nil { + t.Fatal("Funds not deleted") + } +} + +func TestFrozenFundsToDeleteNotExistingFund(t *testing.T) { + b := bus.NewBus() + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + ff, err := NewFrozenFunds(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + ff.Delete(0) +} diff --git a/core/state/frozenfunds/model.go b/core/state/frozenfunds/model.go index 826effb1a..bb9dd5281 100644 --- a/core/state/frozenfunds/model.go +++ b/core/state/frozenfunds/model.go @@ -8,7 +8,8 @@ import ( type Item struct { Address types.Address CandidateKey *types.Pubkey - Coin types.CoinSymbol + CandidateID uint32 + Coin types.CoinID Value *big.Int } @@ -25,10 +26,11 @@ func (m *Model) delete() { m.markDirty(m.height) } -func (m *Model) addFund(address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int) { +func (m *Model) addFund(address types.Address, pubkey types.Pubkey, candidateID uint32, coin types.CoinID, value *big.Int) { m.List = append(m.List, Item{ Address: address, CandidateKey: &pubkey, + CandidateID: candidateID, Coin: coin, Value: value, }) diff --git a/core/state/halts/bus.go b/core/state/halts/bus.go new file mode 100644 index 000000000..d42cb9c36 --- /dev/null +++ b/core/state/halts/bus.go @@ -0,0 +1,17 @@ +package halts + +import ( + "github.com/MinterTeam/minter-go-node/core/types" +) + +type Bus struct { + halts *HaltBlocks +} + +func (b *Bus) AddHaltBlock(height uint64, pubkey types.Pubkey) { + b.halts.AddHaltBlock(height, pubkey) +} + +func NewBus(halts *HaltBlocks) *Bus { + return &Bus{halts: halts} +} diff --git a/core/state/halts/halts.go b/core/state/halts/halts.go new file mode 100644 index 000000000..8f5bda9d9 --- /dev/null +++ b/core/state/halts/halts.go @@ -0,0 +1,202 @@ +package halts + +import ( + "encoding/binary" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "sort" + "sync" +) + +const mainPrefix = byte('h') + +type RHalts interface { + Export(state *types.AppState) + GetHaltBlocks(height uint64) *Model + IsHaltExists(height uint64, pubkey types.Pubkey) bool +} + +type HaltBlocks struct { + list map[uint64]*Model + dirty map[uint64]interface{} + + bus *bus.Bus + iavl tree.MTree + + lock sync.RWMutex +} + +func NewHalts(stateBus *bus.Bus, iavl tree.MTree) (*HaltBlocks, error) { + halts := &HaltBlocks{ + bus: stateBus, + iavl: iavl, + list: map[uint64]*Model{}, + dirty: map[uint64]interface{}{}, + } + + halts.bus.SetHaltBlocks(NewBus(halts)) + + return halts, nil +} + +func (hb *HaltBlocks) Commit() error { + dirty := hb.getOrderedDirty() + for _, height := range dirty { + haltBlock := hb.getFromMap(height) + + hb.lock.Lock() + delete(hb.dirty, height) + hb.lock.Unlock() + + path := getPath(height) + + if haltBlock.deleted { + hb.lock.Lock() + delete(hb.list, height) + hb.lock.Unlock() + + hb.iavl.Remove(path) + } else { + data, err := rlp.EncodeToBytes(haltBlock) + if err != nil { + return fmt.Errorf("can't encode object at %d: %v", height, err) + } + + hb.iavl.Set(path, data) + } + } + + return nil +} + +func (hb *HaltBlocks) GetHaltBlocks(height uint64) *Model { + return hb.get(height) +} + +func (hb *HaltBlocks) GetOrNew(height uint64) *Model { + haltBlock := hb.get(height) + if haltBlock == nil { + haltBlock = &Model{ + height: height, + markDirty: hb.markDirty, + } + hb.setToMap(height, haltBlock) + } + + return haltBlock +} + +func (hb *HaltBlocks) get(height uint64) *Model { + if haltBlock := hb.getFromMap(height); haltBlock != nil { + return haltBlock + } + + _, enc := hb.iavl.Get(getPath(height)) + if len(enc) == 0 { + return nil + } + + haltBlock := &Model{} + if err := rlp.DecodeBytes(enc, haltBlock); err != nil { + panic(fmt.Sprintf("failed to decode halt blocks at height %d: %s", height, err)) + } + + haltBlock.height = height + haltBlock.markDirty = hb.markDirty + + hb.setToMap(height, haltBlock) + + return haltBlock +} + +func (hb *HaltBlocks) markDirty(height uint64) { + hb.dirty[height] = struct{}{} +} + +func (hb *HaltBlocks) getOrderedDirty() []uint64 { + keys := make([]uint64, 0, len(hb.dirty)) + for k := range hb.dirty { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + return keys +} + +func (hb *HaltBlocks) IsHaltExists(height uint64, pubkey types.Pubkey) bool { + model := hb.get(height) + if model == nil { + return false + } + + for _, halt := range model.List { + if halt.Pubkey == pubkey { + return true + } + } + + return false +} + +func (hb *HaltBlocks) AddHaltBlock(height uint64, pubkey types.Pubkey) { + hb.GetOrNew(height).addHaltBlock(pubkey) +} + +func (hb *HaltBlocks) Delete(height uint64) { + haltBlock := hb.get(height) + if haltBlock == nil { + return + } + + haltBlock.delete() +} + +func (hb *HaltBlocks) Export(state *types.AppState) { + hb.iavl.Iterate(func(key []byte, value []byte) bool { + if key[0] != mainPrefix { + return false + } + + height := binary.LittleEndian.Uint64(key[1:]) + halts := hb.get(height) + if halts == nil { + return false + } + + for _, haltBlock := range halts.List { + state.HaltBlocks = append(state.HaltBlocks, types.HaltBlock{ + Height: height, + CandidateKey: haltBlock.Pubkey, + }) + } + + return false + }) +} + +func (hb *HaltBlocks) getFromMap(height uint64) *Model { + hb.lock.RLock() + defer hb.lock.RUnlock() + + return hb.list[height] +} + +func (hb *HaltBlocks) setToMap(height uint64, model *Model) { + hb.lock.Lock() + defer hb.lock.Unlock() + + hb.list[height] = model +} + +func getPath(height uint64) []byte { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, height) + + return append([]byte{mainPrefix}, b...) +} diff --git a/core/state/halts/halts_test.go b/core/state/halts/halts_test.go new file mode 100644 index 000000000..21f0e1394 --- /dev/null +++ b/core/state/halts/halts_test.go @@ -0,0 +1,87 @@ +package halts + +import ( + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/tree" + db "github.com/tendermint/tm-db" + "testing" +) + +func TestHaltsToDeleteModel(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + h, err := NewHalts(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + pubkey, height := types.Pubkey{0}, uint64(10) + + h.AddHaltBlock(height, pubkey) + if err := h.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if h.GetHaltBlocks(height) == nil { + t.Fatal("Halts not found") + } + + h.Delete(height) + if err := h.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + if h.GetHaltBlocks(height) != nil { + t.Fatal("Halts not deleted") + } +} + +func TestBusToAddHaltBlock(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + h, err := NewHalts(bus.NewBus(), mutableTree) + if err != nil { + t.Fatal(err) + } + + pubkey, height := types.Pubkey{0}, uint64(10) + + hbBus := Bus{halts: h} + hbBus.AddHaltBlock(height, pubkey) + + if err := h.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + halt := h.GetHaltBlocks(height) + if halt == nil { + t.Fatal("Halts not found") + } + + if len(halt.List) != 1 { + t.Fatalf("Incorrect amount of halts: %d. Expected: 1", len(halt.List)) + } + + if halt.Height() != height { + t.Fatalf("Invalid height %d. Expected %d", halt.Height(), height) + } + + hbPubKey := halt.List[0].Pubkey + if !hbPubKey.Equals(pubkey) { + t.Fatalf("Invalid public key %s. Expected %s", hbPubKey.String(), pubkey.String()) + } +} diff --git a/core/state/halts/model.go b/core/state/halts/model.go new file mode 100644 index 000000000..932ff6b4e --- /dev/null +++ b/core/state/halts/model.go @@ -0,0 +1,33 @@ +package halts + +import ( + "github.com/MinterTeam/minter-go-node/core/types" +) + +type Item struct { + Pubkey types.Pubkey +} + +type Model struct { + List []Item + + height uint64 + deleted bool + markDirty func(height uint64) +} + +func (m *Model) delete() { + m.deleted = true + m.markDirty(m.height) +} + +func (m *Model) addHaltBlock(pubkey types.Pubkey) { + m.List = append(m.List, Item{ + Pubkey: pubkey, + }) + m.markDirty(m.height) +} + +func (m *Model) Height() uint64 { + return m.height +} diff --git a/core/state/state.go b/core/state/state.go index 4c8db5f3c..71a366fb7 100644 --- a/core/state/state.go +++ b/core/state/state.go @@ -3,7 +3,7 @@ package state import ( "encoding/hex" "fmt" - eventsdb "github.com/MinterTeam/events-db" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/state/app" "github.com/MinterTeam/minter-go-node/core/state/bus" @@ -12,38 +12,165 @@ import ( "github.com/MinterTeam/minter-go-node/core/state/checks" "github.com/MinterTeam/minter-go-node/core/state/coins" "github.com/MinterTeam/minter-go-node/core/state/frozenfunds" + "github.com/MinterTeam/minter-go-node/core/state/halts" "github.com/MinterTeam/minter-go-node/core/state/validators" + "github.com/MinterTeam/minter-go-node/core/state/waitlist" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/helpers" + legacyAccounts "github.com/MinterTeam/minter-go-node/legacy/accounts" + legacyApp "github.com/MinterTeam/minter-go-node/legacy/app" + legacyCandidates "github.com/MinterTeam/minter-go-node/legacy/candidates" + legacyCoins "github.com/MinterTeam/minter-go-node/legacy/coins" + legacyFrozenfunds "github.com/MinterTeam/minter-go-node/legacy/frozenfunds" "github.com/MinterTeam/minter-go-node/tree" db "github.com/tendermint/tm-db" + "gopkg.in/errgo.v2/fmt/errors" "log" "math/big" "sync" ) +type Interface interface { + isValue_State() +} + +type CheckState struct { + state *State +} + +func NewCheckState(state *State) *CheckState { + return &CheckState{state: state} +} + +func (cs *CheckState) isValue_State() {} + +func (cs *CheckState) Lock() { + cs.state.lock.Lock() +} + +func (cs *CheckState) Export(height uint64) types.AppState { + return cs.state.Export(height) +} + +func (cs *CheckState) Unlock() { + cs.state.lock.Unlock() +} + +func (cs *CheckState) RLock() { + cs.state.lock.RLock() +} + +func (cs *CheckState) RUnlock() { + cs.state.lock.RUnlock() +} + +func (cs *CheckState) Validators() validators.RValidators { + return cs.state.Validators +} +func (cs *CheckState) App() app.RApp { + return cs.state.App +} +func (cs *CheckState) Candidates() candidates.RCandidates { + return cs.state.Candidates +} +func (cs *CheckState) FrozenFunds() frozenfunds.RFrozenFunds { + return cs.state.FrozenFunds +} +func (cs *CheckState) Halts() halts.RHalts { + return cs.state.Halts +} +func (cs *CheckState) Accounts() accounts.RAccounts { + return cs.state.Accounts +} +func (cs *CheckState) Coins() coins.RCoins { + return cs.state.Coins +} +func (cs *CheckState) Checks() checks.RChecks { + return cs.state.Checks +} +func (cs *CheckState) WaitList() waitlist.RWaitList { + return cs.state.Waitlist +} +func (cs *CheckState) Tree() tree.ReadOnlyTree { + return cs.state.Tree() +} + +func (cs *CheckState) Export11To12(height uint64) types.AppState { + iavlTree := cs.state.tree + + candidatesState, err := legacyCandidates.NewCandidates(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + validatorsState, err := validators.NewValidators(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + appState, err := legacyApp.NewApp(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + frozenFundsState, err := legacyFrozenfunds.NewFrozenFunds(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + accountsState, err := legacyAccounts.NewAccounts(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + coinsState, err := legacyCoins.NewCoins(nil, iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + checksState, err := checks.NewChecks(iavlTree) + if err != nil { + log.Panicf("Create new state at height %d failed: %s", height, err) + } + + state := new(types.AppState) + appState.Export(state, height) + coinsMap := coinsState.Export(state) + validatorsState.Export(state) + candidatesMap := candidatesState.Export(state, coinsMap) + frozenFundsState.Export(state, height, coinsMap, candidatesMap) + accountsState.Export(state, coinsMap) + checksState.Export(state) + coinsState.Export(state) + + return *state +} + type State struct { App *app.App Validators *validators.Validators Candidates *candidates.Candidates FrozenFunds *frozenfunds.FrozenFunds + Halts *halts.HaltBlocks Accounts *accounts.Accounts Coins *coins.Coins Checks *checks.Checks Checker *checker.Checker + Waitlist *waitlist.WaitList db db.DB events eventsdb.IEventsDB - tree tree.Tree + tree tree.MTree keepLastStates int64 bus *bus.Bus lock sync.RWMutex } -func NewState(height uint64, db db.DB, events eventsdb.IEventsDB, keepLastStates int64, cacheSize int) (*State, error) { - iavlTree := tree.NewMutableTree(db, cacheSize) - _, err := iavlTree.LoadVersion(int64(height)) +func (s *State) isValue_State() {} + +func NewState(height uint64, db db.DB, events eventsdb.IEventsDB, cacheSize int, keepLastStates int64) (*State, error) { + iavlTree, err := tree.NewMutableTree(height, db, cacheSize) if err != nil { return nil, err } @@ -53,25 +180,23 @@ func NewState(height uint64, db db.DB, events eventsdb.IEventsDB, keepLastStates return nil, err } - state.Candidates.LoadCandidates() + state.Candidates.LoadCandidatesDeliver() state.Candidates.LoadStakes() state.Validators.LoadValidators() return state, nil } -func NewCheckState(state *State) *State { - return state -} - -func NewCheckStateAtHeight(height uint64, db db.DB) (*State, error) { - iavlTree := tree.NewMutableTree(db, 1024) - _, err := iavlTree.LazyLoadVersion(int64(height)) +func NewCheckStateAtHeight(height uint64, db db.DB) (*CheckState, error) { + iavlTree, err := tree.NewImmutableTree(height, db) if err != nil { return nil, err } + return newCheckStateForTree(iavlTree, nil, db, 0) +} - return newStateForTree(iavlTree.GetImmutable(), nil, nil, 0) +func (s *State) Tree() tree.MTree { + return s.tree } func (s *State) Lock() { @@ -99,16 +224,21 @@ func (s *State) Check() error { } if delta.Cmp(volume) != 0 { - return fmt.Errorf("invariants error on coin %s: %s", coin.String(), big.NewInt(0).Sub(volumeDeltas[coin], delta).String()) + return fmt.Errorf("invariants error on coin %s: %s", coin.String(), big.NewInt(0).Sub(volume, delta).String()) } } return nil } +const countBatchBlocksDelete = 60 + func (s *State) Commit() ([]byte, error) { s.Checker.Reset() + s.tree.GlobalLock() + defer s.tree.GlobalUnlock() + if err := s.Accounts.Commit(); err != nil { return nil, err } @@ -137,33 +267,52 @@ func (s *State) Commit() ([]byte, error) { return nil, err } + if err := s.Halts.Commit(); err != nil { + return nil, err + } + + if err := s.Waitlist.Commit(); err != nil { + return nil, err + } + hash, version, err := s.tree.SaveVersion() + if err != nil { + return hash, err + } - if s.keepLastStates < version-1 { - _ = s.tree.DeleteVersion(version - s.keepLastStates) + if version%countBatchBlocksDelete == 30 && version-countBatchBlocksDelete > s.keepLastStates { + if err := s.tree.DeleteVersionsIfExists(version-countBatchBlocksDelete-s.keepLastStates, version-s.keepLastStates); err != nil { + return hash, err + } } - return hash, err + return hash, nil } func (s *State) Import(state types.AppState) error { s.App.SetMaxGas(state.MaxGas) s.App.SetTotalSlashed(helpers.StringToBigInt(state.TotalSlashed)) + s.App.SetCoinsCount(uint32(len(state.Coins))) for _, a := range state.Accounts { if a.MultisigData != nil { - s.Accounts.CreateMultisig(a.MultisigData.Weights, a.MultisigData.Addresses, a.MultisigData.Threshold, 1) + var weights []uint32 + for _, weight := range a.MultisigData.Weights { + weights = append(weights, uint32(weight)) + } + s.Accounts.CreateMultisig(weights, a.MultisigData.Addresses, uint32(a.MultisigData.Threshold), a.Address) } s.Accounts.SetNonce(a.Address, a.Nonce) for _, b := range a.Balance { - s.Accounts.SetBalance(a.Address, b.Coin, helpers.StringToBigInt(b.Value)) + s.Accounts.SetBalance(a.Address, types.CoinID(b.Coin), helpers.StringToBigInt(b.Value)) } } for _, c := range state.Coins { - s.Coins.Create(c.Symbol, c.Name, helpers.StringToBigInt(c.Volume), c.Crr, helpers.StringToBigInt(c.Reserve), helpers.StringToBigInt(c.MaxSupply)) + s.Coins.Create(types.CoinID(c.ID), c.Symbol, c.Name, helpers.StringToBigInt(c.Volume), + uint32(c.Crr), helpers.StringToBigInt(c.Reserve), helpers.StringToBigInt(c.MaxSupply), c.OwnerAddress) } var vals []*validators.Validator @@ -180,8 +329,12 @@ func (s *State) Import(state types.AppState) error { } s.Validators.SetValidators(vals) + for _, pubkey := range state.BlockListCandidates { + s.Candidates.AddToBlockPubKey(pubkey) + } + for _, c := range state.Candidates { - s.Candidates.Create(c.OwnerAddress, c.RewardAddress, c.PubKey, c.Commission) + s.Candidates.CreateWithID(c.OwnerAddress, c.RewardAddress, c.ControlAddress, c.PubKey, uint32(c.Commission), uint32(c.ID)) if c.Status == candidates.CandidateStatusOnline { s.Candidates.SetOnline(c.PubKey) } @@ -189,6 +342,15 @@ func (s *State) Import(state types.AppState) error { s.Candidates.SetTotalStake(c.PubKey, helpers.StringToBigInt(c.TotalBipStake)) s.Candidates.SetStakes(c.PubKey, c.Stakes, c.Updates) } + s.Candidates.RecalculateStakes(state.StartHeight) + + for _, w := range state.Waitlist { + value, ok := big.NewInt(0).SetString(w.Value, 10) + if !ok { + return errors.Newf("Cannot decode %s into big.Int", w.Value) + } + s.Waitlist.AddWaitList(w.Owner, s.Candidates.PubKey(uint32(w.CandidateID)), types.CoinID(w.Coin), value) + } for _, hashString := range state.UsedChecks { bytes, _ := hex.DecodeString(string(hashString)) @@ -198,7 +360,7 @@ func (s *State) Import(state types.AppState) error { } for _, ff := range state.FrozenFunds { - s.FrozenFunds.AddFund(ff.Height, ff.Address, *ff.CandidateKey, ff.Coin, helpers.StringToBigInt(ff.Value)) + s.FrozenFunds.AddFund(ff.Height, ff.Address, *ff.CandidateKey, uint32(ff.CandidateID), types.CoinID(ff.Coin), helpers.StringToBigInt(ff.Value)) } return nil @@ -211,18 +373,29 @@ func (s *State) Export(height uint64) types.AppState { } appState := new(types.AppState) - state.App.Export(appState, height) - state.Validators.Export(appState) - state.Candidates.Export(appState) - state.FrozenFunds.Export(appState, height) - state.Accounts.Export(appState) - state.Coins.Export(appState) - state.Checks.Export(appState) + state.App().Export(appState, height) + state.Validators().Export(appState) + state.Candidates().Export(appState) + state.WaitList().Export(appState) + state.FrozenFunds().Export(appState, height) + state.Accounts().Export(appState) + state.Coins().Export(appState) + state.Checks().Export(appState) + state.Halts().Export(appState) return *appState } -func newStateForTree(iavlTree tree.Tree, events eventsdb.IEventsDB, db db.DB, keepLastStates int64) (*State, error) { +func newCheckStateForTree(iavlTree tree.MTree, events eventsdb.IEventsDB, db db.DB, keepLastStates int64) (*CheckState, error) { + stateForTree, err := newStateForTree(iavlTree, events, db, keepLastStates) + if err != nil { + return nil, err + } + + return NewCheckState(stateForTree), nil +} + +func newStateForTree(iavlTree tree.MTree, events eventsdb.IEventsDB, db db.DB, keepLastStates int64) (*State, error) { stateBus := bus.NewBus() stateBus.SetEvents(events) @@ -263,6 +436,16 @@ func newStateForTree(iavlTree tree.Tree, events eventsdb.IEventsDB, db db.DB, ke return nil, err } + haltsState, err := halts.NewHalts(stateBus, iavlTree) + if err != nil { + return nil, err + } + + waitlistState, err := waitlist.NewWaitList(stateBus, iavlTree) + if err != nil { + return nil, err + } + state := &State{ Validators: validatorsState, App: appState, @@ -272,7 +455,10 @@ func newStateForTree(iavlTree tree.Tree, events eventsdb.IEventsDB, db db.DB, ke Coins: coinsState, Checks: checksState, Checker: stateChecker, - bus: stateBus, + Halts: haltsState, + Waitlist: waitlistState, + + bus: stateBus, db: db, events: events, diff --git a/core/state/state_test.go b/core/state/state_test.go index cc3637c46..f1c26d535 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -23,33 +23,42 @@ func TestStateExport(t *testing.T) { coinTest := types.StrToCoinSymbol("TEST") coinTest2 := types.StrToCoinSymbol("TEST2") + coinTestID := state.App.GetNextCoinID() + coinTest2ID := coinTestID + 1 + state.Coins.Create( + coinTestID, coinTest, "TEST", - helpers.BipToPip(big.NewInt(1)), + helpers.BipToPip(big.NewInt(602)), 10, helpers.BipToPip(big.NewInt(100)), helpers.BipToPip(big.NewInt(100)), + nil, ) state.Coins.Create( + coinTest2ID, coinTest2, "TEST2", - helpers.BipToPip(big.NewInt(2)), + helpers.BipToPip(big.NewInt(1004)), 50, helpers.BipToPip(big.NewInt(200)), helpers.BipToPip(big.NewInt(200)), + nil, ) + state.App.SetCoinsCount(coinTest2ID.Uint32()) + privateKey1, _ := crypto.GenerateKey() address1 := crypto.PubkeyToAddress(privateKey1.PublicKey) privateKey2, _ := crypto.GenerateKey() address2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - state.Accounts.AddBalance(address1, types.GetBaseCoin(), helpers.BipToPip(big.NewInt(1))) - state.Accounts.AddBalance(address1, coinTest, helpers.BipToPip(big.NewInt(1))) - state.Accounts.AddBalance(address2, coinTest2, helpers.BipToPip(big.NewInt(2))) + state.Accounts.AddBalance(address1, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(1))) + state.Accounts.AddBalance(address1, coinTestID, helpers.BipToPip(big.NewInt(1))) + state.Accounts.AddBalance(address2, coinTest2ID, helpers.BipToPip(big.NewInt(2))) candidatePubKey1 := [32]byte{} rand.Read(candidatePubKey1[:]) @@ -57,21 +66,21 @@ func TestStateExport(t *testing.T) { candidatePubKey2 := [32]byte{} rand.Read(candidatePubKey2[:]) - state.Candidates.Create(address1, address1, candidatePubKey1, 10) - state.Candidates.Create(address2, address2, candidatePubKey2, 30) + state.Candidates.Create(address1, address1, address1, candidatePubKey1, 10) + state.Candidates.Create(address2, address2, address2, candidatePubKey2, 30) state.Validators.Create(candidatePubKey1, helpers.BipToPip(big.NewInt(1))) - state.FrozenFunds.AddFund(height, address1, candidatePubKey1, coinTest, helpers.BipToPip(big.NewInt(100))) - state.FrozenFunds.AddFund(height+10, address1, candidatePubKey1, types.GetBaseCoin(), helpers.BipToPip(big.NewInt(3))) - state.FrozenFunds.AddFund(height+100, address2, candidatePubKey1, coinTest, helpers.BipToPip(big.NewInt(500))) - state.FrozenFunds.AddFund(height+150, address2, candidatePubKey1, coinTest2, helpers.BipToPip(big.NewInt(1000))) + state.FrozenFunds.AddFund(height, address1, candidatePubKey1, state.Candidates.ID(candidatePubKey1), coinTestID, helpers.BipToPip(big.NewInt(100))) + state.FrozenFunds.AddFund(height+10, address1, candidatePubKey1, state.Candidates.ID(candidatePubKey1), types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(3))) + state.FrozenFunds.AddFund(height+100, address2, candidatePubKey1, state.Candidates.ID(candidatePubKey1), coinTestID, helpers.BipToPip(big.NewInt(500))) + state.FrozenFunds.AddFund(height+150, address2, candidatePubKey1, state.Candidates.ID(candidatePubKey1), coinTest2ID, helpers.BipToPip(big.NewInt(1000))) newCheck := &check.Check{ Nonce: []byte("test nonce"), ChainID: types.CurrentChainID, DueBlock: height + 1, - Coin: coinTest, + Coin: coinTestID, Value: helpers.BipToPip(big.NewInt(100)), - GasCoin: coinTest2, + GasCoin: coinTest2ID, } err = newCheck.Sign(privateKey1) @@ -81,12 +90,25 @@ func TestStateExport(t *testing.T) { state.Checks.UseCheck(newCheck) + state.Halts.AddHaltBlock(height, types.Pubkey{0}) + state.Halts.AddHaltBlock(height+1, types.Pubkey{1}) + state.Halts.AddHaltBlock(height+2, types.Pubkey{2}) + + wlAddr1 := types.StringToAddress("1") + wlAddr2 := types.StringToAddress("2") + + state.Waitlist.AddWaitList(wlAddr1, candidatePubKey1, coinTestID, big.NewInt(1e18)) + state.Waitlist.AddWaitList(wlAddr2, candidatePubKey2, coinTest2ID, big.NewInt(2e18)) + _, err = state.Commit() if err != nil { log.Panicf("Cannot commit state: %s", err) } newState := state.Export(height) + if err := newState.Verify(); err != nil { + t.Error(err) + } if newState.StartHeight != height { t.Fatalf("Wrong new state start height. Expected %d, got %d", height, newState.StartHeight) @@ -104,12 +126,12 @@ func TestStateExport(t *testing.T) { t.Fatalf("Wrong new state coins size. Expected %d, got %d", 2, len(newState.Coins)) } - newStateCoin := newState.Coins[0] - newStateCoin1 := newState.Coins[1] + newStateCoin := newState.Coins[1] + newStateCoin1 := newState.Coins[0] if newStateCoin.Name != "TEST" || newStateCoin.Symbol != coinTest || - newStateCoin.Volume != helpers.BipToPip(big.NewInt(1)).String() || + newStateCoin.Volume != helpers.BipToPip(big.NewInt(602)).String() || newStateCoin.Reserve != helpers.BipToPip(big.NewInt(100)).String() || newStateCoin.MaxSupply != helpers.BipToPip(big.NewInt(100)).String() || newStateCoin.Crr != 10 { @@ -118,7 +140,7 @@ func TestStateExport(t *testing.T) { if newStateCoin1.Name != "TEST2" || newStateCoin1.Symbol != coinTest2 || - newStateCoin1.Volume != helpers.BipToPip(big.NewInt(2)).String() || + newStateCoin1.Volume != helpers.BipToPip(big.NewInt(1004)).String() || newStateCoin1.Reserve != helpers.BipToPip(big.NewInt(200)).String() || newStateCoin1.MaxSupply != helpers.BipToPip(big.NewInt(200)).String() || newStateCoin1.Crr != 50 { @@ -136,7 +158,7 @@ func TestStateExport(t *testing.T) { if funds.Height != height || funds.Address != address1 || - funds.Coin != coinTest || + funds.Coin != uint64(coinTestID) || *funds.CandidateKey != types.Pubkey(candidatePubKey1) || funds.Value != helpers.BipToPip(big.NewInt(100)).String() { t.Fatalf("Wrong new state frozen fund data") @@ -144,7 +166,7 @@ func TestStateExport(t *testing.T) { if funds1.Height != height+10 || funds1.Address != address1 || - funds1.Coin != types.GetBaseCoin() || + funds1.Coin != uint64(types.GetBaseCoinID()) || *funds1.CandidateKey != types.Pubkey(candidatePubKey1) || funds1.Value != helpers.BipToPip(big.NewInt(3)).String() { t.Fatalf("Wrong new state frozen fund data") @@ -152,7 +174,7 @@ func TestStateExport(t *testing.T) { if funds2.Height != height+100 || funds2.Address != address2 || - funds2.Coin != coinTest || + funds2.Coin != uint64(coinTestID) || *funds2.CandidateKey != types.Pubkey(candidatePubKey1) || funds2.Value != helpers.BipToPip(big.NewInt(500)).String() { t.Fatalf("Wrong new state frozen fund data") @@ -160,7 +182,7 @@ func TestStateExport(t *testing.T) { if funds3.Height != height+150 || funds3.Address != address2 || - funds3.Coin != coinTest2 || + funds3.Coin != uint64(coinTest2ID) || *funds3.CandidateKey != types.Pubkey(candidatePubKey1) || funds3.Value != helpers.BipToPip(big.NewInt(1000)).String() { t.Fatalf("Wrong new state frozen fund data") @@ -198,15 +220,15 @@ func TestStateExport(t *testing.T) { t.Fatal("Wrong new state account balances size") } - if account1.Balance[0].Coin != coinTest || account1.Balance[0].Value != helpers.BipToPip(big.NewInt(1)).String() { + if account1.Balance[0].Coin != uint64(coinTestID) || account1.Balance[0].Value != helpers.BipToPip(big.NewInt(1)).String() { t.Fatal("Wrong new state account balance data") } - if account1.Balance[1].Coin != types.GetBaseCoin() || account1.Balance[1].Value != helpers.BipToPip(big.NewInt(1)).String() { + if account1.Balance[1].Coin != uint64(types.GetBaseCoinID()) || account1.Balance[1].Value != helpers.BipToPip(big.NewInt(1)).String() { t.Fatal("Wrong new state account balance data") } - if account2.Balance[0].Coin != coinTest2 || account2.Balance[0].Value != helpers.BipToPip(big.NewInt(2)).String() { + if account2.Balance[0].Coin != uint64(coinTest2ID) || account2.Balance[0].Value != helpers.BipToPip(big.NewInt(2)).String() { t.Fatal("Wrong new state account balance data") } @@ -238,4 +260,35 @@ func TestStateExport(t *testing.T) { newStateCandidate2.Commission != 30 { t.Fatal("Wrong new state candidate data") } + + if len(newState.HaltBlocks) != 3 { + t.Fatalf("Invalid amount of halts: %d. Expected 3", len(newState.HaltBlocks)) + } + + pubkey := types.Pubkey{0} + if newState.HaltBlocks[0].Height != height || !newState.HaltBlocks[0].CandidateKey.Equals(pubkey) { + t.Fatal("Wrong new state halt blocks") + } + + pubkey = types.Pubkey{1} + if newState.HaltBlocks[1].Height != height+1 || !newState.HaltBlocks[1].CandidateKey.Equals(pubkey) { + t.Fatal("Wrong new state halt blocks") + } + + pubkey = types.Pubkey{2} + if newState.HaltBlocks[2].Height != height+2 || !newState.HaltBlocks[2].CandidateKey.Equals(pubkey) { + t.Fatal("Wrong new state halt blocks") + } + + if len(newState.Waitlist) != 2 { + t.Fatalf("Invalid amount of waitlist: %d. Expected 2", len(newState.Waitlist)) + } + + if newState.Waitlist[0].Coin != uint64(coinTest2ID) || newState.Waitlist[0].Value != big.NewInt(2e18).String() || newState.Waitlist[0].Owner.Compare(wlAddr2) != 0 { + t.Fatal("Invalid waitlist data") + } + + if newState.Waitlist[1].Coin != uint64(coinTestID) || newState.Waitlist[1].Value != big.NewInt(1e18).String() || newState.Waitlist[1].Owner.Compare(wlAddr1) != 0 { + t.Fatal("Invalid waitlist data") + } } diff --git a/core/state/validators/model.go b/core/state/validators/model.go index fa869d579..1d5c0a510 100644 --- a/core/state/validators/model.go +++ b/core/state/validators/model.go @@ -47,7 +47,7 @@ func (v *Validator) SetAccumReward(value *big.Int) { if v.accumReward.Cmp(value) != 0 { v.isAccumRewardDirty = true } - v.bus.Checker().AddCoin(types.GetBaseCoin(), big.NewInt(0).Sub(value, v.accumReward), "reward") + v.bus.Checker().AddCoin(types.GetBaseCoinID(), big.NewInt(0).Sub(value, v.accumReward), "reward") v.accumReward = big.NewInt(0).Set(value) } @@ -55,19 +55,23 @@ func (v *Validator) GetAccumReward() *big.Int { return big.NewInt(0).Set(v.accumReward) } +// GetAddress returns tendermint-address of a validator func (v *Validator) GetAddress() types.TmAddress { return v.tmAddress } +// GetTotalBipStake returns total bip stake func (v *Validator) GetTotalBipStake() *big.Int { return big.NewInt(0).Set(v.totalStake) } +// SetTotalBipStake sets total bip stake func (v *Validator) SetTotalBipStake(value *big.Int) { - if v.totalStake.Cmp(value) != 0 { - v.isTotalStakeDirty = true + if v.totalStake.Cmp(value) == 0 { + return } - v.totalStake = value + v.isTotalStakeDirty = true + v.totalStake = big.NewInt(0).Set(value) } func (v *Validator) AddAccumReward(amount *big.Int) { @@ -77,7 +81,7 @@ func (v *Validator) AddAccumReward(amount *big.Int) { func (v *Validator) CountAbsentTimes() int { count := 0 - for i := 0; i < ValidatorMaxAbsentWindow; i++ { + for i := 0; i < validatorMaxAbsentWindow; i++ { if v.AbsentTimes.GetIndex(i) { count++ } @@ -98,7 +102,7 @@ func (v *Validator) setTmAddress() { } func (v *Validator) SetPresent(height uint64) { - index := int(height) % ValidatorMaxAbsentWindow + index := int(height) % validatorMaxAbsentWindow if v.AbsentTimes.GetIndex(index) { v.isDirty = true } @@ -106,7 +110,7 @@ func (v *Validator) SetPresent(height uint64) { } func (v *Validator) SetAbsent(height uint64) { - index := int(height) % ValidatorMaxAbsentWindow + index := int(height) % validatorMaxAbsentWindow if !v.AbsentTimes.GetIndex(index) { v.isDirty = true } diff --git a/core/state/validators/validators.go b/core/state/validators/validators.go index 95e720d01..fd0dffc48 100644 --- a/core/state/validators/validators.go +++ b/core/state/validators/validators.go @@ -2,15 +2,16 @@ package validators import ( "fmt" - eventsdb "github.com/MinterTeam/events-db" "github.com/MinterTeam/minter-go-node/core/dao" "github.com/MinterTeam/minter-go-node/core/developers" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" "github.com/MinterTeam/minter-go-node/core/state/bus" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" ) @@ -21,24 +22,36 @@ const ( ) const ( - ValidatorMaxAbsentWindow = 24 - ValidatorMaxAbsentTimes = 12 + validatorMaxAbsentWindow = 24 + validatorMaxAbsentTimes = 12 ) +// Validators struct is a store of Validators state type Validators struct { list []*Validator loaded bool - iavl tree.Tree + iavl tree.MTree bus *bus.Bus } -func NewValidators(bus *bus.Bus, iavl tree.Tree) (*Validators, error) { +// RValidators interface represents Validator state +type RValidators interface { + GetValidators() []*Validator + Export(state *types.AppState) + GetByPublicKey(pubKey types.Pubkey) *Validator + LoadValidators() + GetByTmAddress(address types.TmAddress) *Validator +} + +// NewValidators returns newly created Validators state with a given bus and iavl +func NewValidators(bus *bus.Bus, iavl tree.MTree) (*Validators, error) { validators := &Validators{iavl: iavl, bus: bus} return validators, nil } +// Commit writes changes to iavl, may return an error func (v *Validators) Commit() error { if v.hasDirtyValidators() { data, err := rlp.EncodeToBytes(v.list) @@ -73,6 +86,7 @@ func (v *Validators) Commit() error { return nil } +// SetValidatorPresent marks validator as present at current height func (v *Validators) SetValidatorPresent(height uint64, address types.TmAddress) { validator := v.GetByTmAddress(address) if validator == nil { @@ -81,6 +95,8 @@ func (v *Validators) SetValidatorPresent(height uint64, address types.TmAddress) validator.SetPresent(height) } +// SetValidatorAbsent marks validator as absent at current height +// if validator misses signs of more than validatorMaxAbsentTimes, it will receive penalty and will be swithed off func (v *Validators) SetValidatorAbsent(height uint64, address types.TmAddress) { validator := v.GetByTmAddress(address) if validator == nil { @@ -88,7 +104,7 @@ func (v *Validators) SetValidatorAbsent(height uint64, address types.TmAddress) } validator.SetAbsent(height) - if validator.CountAbsentTimes() > ValidatorMaxAbsentTimes { + if validator.CountAbsentTimes() > validatorMaxAbsentTimes { if !upgrades.IsGraceBlock(height) { v.punishValidator(height, address) } @@ -97,17 +113,19 @@ func (v *Validators) SetValidatorAbsent(height uint64, address types.TmAddress) } } +// GetValidators returns list of validators func (v *Validators) GetValidators() []*Validator { return v.list } +// SetNewValidators updated validators list with new candidates func (v *Validators) SetNewValidators(candidates []candidates.Candidate) { old := v.GetValidators() var newVals []*Validator for _, candidate := range candidates { accumReward := big.NewInt(0) - absentTimes := types.NewBitArray(ValidatorMaxAbsentWindow) + absentTimes := types.NewBitArray(validatorMaxAbsentWindow) for _, oldVal := range old { if oldVal.GetAddress() == candidate.GetTmAddress() { @@ -129,9 +147,12 @@ func (v *Validators) SetNewValidators(candidates []candidates.Candidate) { }) } - v.list = newVals + v.SetValidators(newVals) } +// PunishByzantineValidator find validator with given tmAddress and punishes it: +// 1. Set total stake 0 +// 2. Drop validator func (v *Validators) PunishByzantineValidator(tmAddress [20]byte) { validator := v.GetByTmAddress(tmAddress) if validator != nil { @@ -141,11 +162,12 @@ func (v *Validators) PunishByzantineValidator(tmAddress [20]byte) { } } +// Create creates a new validator with given params and adds it to state func (v *Validators) Create(pubkey types.Pubkey, stake *big.Int) { val := &Validator{ PubKey: pubkey, - AbsentTimes: types.NewBitArray(ValidatorMaxAbsentWindow), - totalStake: stake, + AbsentTimes: types.NewBitArray(validatorMaxAbsentWindow), + totalStake: big.NewInt(0).Set(stake), accumReward: big.NewInt(0), isDirty: true, isTotalStakeDirty: true, @@ -156,6 +178,7 @@ func (v *Validators) Create(pubkey types.Pubkey, stake *big.Int) { v.list = append(v.list, val) } +// PayRewards distributes accumulated rewards between validator, delegators, DAO and developers addresses func (v *Validators) PayRewards(height uint64) { vals := v.GetValidators() for _, validator := range vals { @@ -169,9 +192,9 @@ func (v *Validators) PayRewards(height uint64) { DAOReward := big.NewInt(0).Set(totalReward) DAOReward.Mul(DAOReward, big.NewInt(int64(dao.Commission))) DAOReward.Div(DAOReward, big.NewInt(100)) - v.bus.Accounts().AddBalance(dao.Address, types.GetBaseCoin(), DAOReward) + v.bus.Accounts().AddBalance(dao.Address, types.GetBaseCoinID(), DAOReward) remainder.Sub(remainder, DAOReward) - v.bus.Events().AddEvent(uint32(height), eventsdb.RewardEvent{ + v.bus.Events().AddEvent(uint32(height), &eventsdb.RewardEvent{ Role: eventsdb.RoleDAO.String(), Address: dao.Address, Amount: DAOReward.String(), @@ -182,9 +205,9 @@ func (v *Validators) PayRewards(height uint64) { DevelopersReward := big.NewInt(0).Set(totalReward) DevelopersReward.Mul(DevelopersReward, big.NewInt(int64(developers.Commission))) DevelopersReward.Div(DevelopersReward, big.NewInt(100)) - v.bus.Accounts().AddBalance(developers.Address, types.GetBaseCoin(), DevelopersReward) + v.bus.Accounts().AddBalance(developers.Address, types.GetBaseCoinID(), DevelopersReward) remainder.Sub(remainder, DevelopersReward) - v.bus.Events().AddEvent(uint32(height), eventsdb.RewardEvent{ + v.bus.Events().AddEvent(uint32(height), &eventsdb.RewardEvent{ Role: eventsdb.RoleDevelopers.String(), Address: developers.Address, Amount: DevelopersReward.String(), @@ -199,9 +222,9 @@ func (v *Validators) PayRewards(height uint64) { validatorReward.Mul(validatorReward, big.NewInt(int64(candidate.Commission))) validatorReward.Div(validatorReward, big.NewInt(100)) totalReward.Sub(totalReward, validatorReward) - v.bus.Accounts().AddBalance(candidate.RewardAddress, types.GetBaseCoin(), validatorReward) + v.bus.Accounts().AddBalance(candidate.RewardAddress, types.GetBaseCoinID(), validatorReward) remainder.Sub(remainder, validatorReward) - v.bus.Events().AddEvent(uint32(height), eventsdb.RewardEvent{ + v.bus.Events().AddEvent(uint32(height), &eventsdb.RewardEvent{ Role: eventsdb.RoleValidator.String(), Address: candidate.RewardAddress, Amount: validatorReward.String(), @@ -222,10 +245,10 @@ func (v *Validators) PayRewards(height uint64) { continue } - v.bus.Accounts().AddBalance(stake.Owner, types.GetBaseCoin(), reward) + v.bus.Accounts().AddBalance(stake.Owner, types.GetBaseCoinID(), reward) remainder.Sub(remainder, reward) - v.bus.Events().AddEvent(uint32(height), eventsdb.RewardEvent{ + v.bus.Events().AddEvent(uint32(height), &eventsdb.RewardEvent{ Role: eventsdb.RoleDelegator.String(), Address: stake.Owner, Amount: reward.String(), @@ -244,6 +267,7 @@ func (v *Validators) PayRewards(height uint64) { } } +// GetByTmAddress finds and returns validator with given tendermint-address func (v *Validators) GetByTmAddress(address types.TmAddress) *Validator { for _, val := range v.list { if val.tmAddress == address { @@ -254,6 +278,7 @@ func (v *Validators) GetByTmAddress(address types.TmAddress) *Validator { return nil } +// GetByPublicKey finds and returns validator func (v *Validators) GetByPublicKey(pubKey types.Pubkey) *Validator { for _, val := range v.list { if val.PubKey == pubKey { @@ -264,6 +289,7 @@ func (v *Validators) GetByPublicKey(pubKey types.Pubkey) *Validator { return nil } +// LoadValidators loads only list of validators (for read) func (v *Validators) LoadValidators() { if v.loaded { return @@ -333,10 +359,12 @@ func (v *Validators) punishValidator(height uint64, tmAddress types.TmAddress) { validator.SetTotalBipStake(totalStake) } +// SetValidators updates validators list func (v *Validators) SetValidators(vals []*Validator) { v.list = vals } +// Export exports all data to the given state func (v *Validators) Export(state *types.AppState) { v.LoadValidators() @@ -350,6 +378,7 @@ func (v *Validators) Export(state *types.AppState) { } } +// SetToDrop marks given validator as inactive for dropping it in the next block func (v *Validators) SetToDrop(pubkey types.Pubkey) { vals := v.GetValidators() for _, val := range vals { @@ -361,9 +390,9 @@ func (v *Validators) SetToDrop(pubkey types.Pubkey) { func (v *Validators) turnValidatorOff(tmAddress types.TmAddress) { validator := v.GetByTmAddress(tmAddress) - validator.AbsentTimes = types.NewBitArray(ValidatorMaxAbsentWindow) + validator.AbsentTimes = types.NewBitArray(validatorMaxAbsentWindow) validator.toDrop = true validator.isDirty = true - v.bus.Candidates().SetOffline(v.bus.Candidates().GetCandidateByTendermintAddress(tmAddress).PubKey) + v.bus.Candidates().SetOffline(validator.PubKey) } diff --git a/core/state/validators/validators_test.go b/core/state/validators/validators_test.go new file mode 100644 index 000000000..abf2a2c8b --- /dev/null +++ b/core/state/validators/validators_test.go @@ -0,0 +1,460 @@ +package validators + +import ( + "encoding/json" + "fmt" + "github.com/MinterTeam/minter-go-node/core/dao" + "github.com/MinterTeam/minter-go-node/core/developers" + eventsdb "github.com/MinterTeam/minter-go-node/core/events" + "github.com/MinterTeam/minter-go-node/core/state/accounts" + "github.com/MinterTeam/minter-go-node/core/state/app" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/candidates" + "github.com/MinterTeam/minter-go-node/core/state/checker" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/tree" + db "github.com/tendermint/tm-db" + "math/big" + "testing" +) + +func TestValidators_GetValidators(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{1}, big.NewInt(1000000)) + validators.Create([32]byte{2}, big.NewInt(2000000)) + getValidators := validators.GetValidators() + if len(getValidators) != 2 { + t.Fatal("count of validators not equal 2") + } + if getValidators[0].PubKey != [32]byte{1} { + t.Fatal("validator public_key invalid") + } + if getValidators[0].totalStake.String() != "1000000" { + t.Fatal("validator total_stake invalid") + } + if getValidators[1].PubKey != [32]byte{2} { + t.Fatal("validator public_key invalid") + } + if getValidators[1].totalStake.String() != "2000000" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_GetByPublicKey(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{1}, big.NewInt(1000000)) + validator := validators.GetByPublicKey([32]byte{1}) + if validator == nil { + t.Fatal("validator not found") + } + if validator.PubKey != [32]byte{1} { + t.Fatal("validator public_key invalid") + } + if validator.totalStake.String() != "1000000" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_GetByTmAddress(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{1}, big.NewInt(1000000)) + validator := validators.GetByPublicKey([32]byte{1}) + if validator == nil { + t.Fatal("validator not found") + } + vldtr := validators.GetByTmAddress(validator.tmAddress) + + if vldtr.PubKey != [32]byte{1} { + t.Fatal("validator public_key invalid") + } + if vldtr.totalStake.String() != "1000000" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_PunishByzantineValidator(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{1}, big.NewInt(1000000)) + validator := validators.GetByPublicKey([32]byte{1}) + if validator == nil { + t.Fatal("validator not found") + } + + validators.PunishByzantineValidator(validator.tmAddress) + + if validator.totalStake.String() != "0" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_LoadValidators(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + newValidator := NewValidator( + [32]byte{1}, + types.NewBitArray(validatorMaxAbsentWindow), + big.NewInt(1000000), + big.NewInt(0), + true, + true, + true, + b) + newValidator.AddAccumReward(big.NewInt(10)) + validators.SetValidators([]*Validator{newValidator}) + + validators.Create([32]byte{2}, big.NewInt(2000000)) + + err = validators.Commit() + if err != nil { + t.Fatal(err) + } + + validators, err = NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.LoadValidators() + + getValidators := validators.GetValidators() + if len(getValidators) != 2 { + t.Fatal("count of validators not equal 2") + } + if getValidators[0].PubKey != [32]byte{1} { + t.Fatal("validator public_key invalid") + } + if getValidators[0].totalStake.String() != "1000000" { + t.Fatal("validator total_stake invalid") + } + if getValidators[1].PubKey != [32]byte{2} { + t.Fatal("validator public_key invalid") + } + if getValidators[1].totalStake.String() != "2000000" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_SetValidators(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + newValidator := NewValidator( + [32]byte{1}, + types.NewBitArray(validatorMaxAbsentWindow), + big.NewInt(1000000), + big.NewInt(0), + true, + true, + true, + b) + validators.SetValidators([]*Validator{newValidator}) + + validator := validators.GetByPublicKey([32]byte{1}) + if validator == nil { + t.Fatal("validator not found") + } + if validator.PubKey != [32]byte{1} { + t.Fatal("validator public_key invalid") + } + if validator.totalStake.String() != "1000000" { + t.Fatal("validator total_stake invalid") + } +} + +func TestValidators_PayRewards(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + b.SetChecker(checker.NewChecker(b)) + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + appBus, err := app.NewApp(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetApp(appBus) + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + newValidator := NewValidator( + [32]byte{4}, + types.NewBitArray(validatorMaxAbsentWindow), + big.NewInt(1000000), + big.NewInt(10), + true, + true, + true, + b) + validators.SetValidators([]*Validator{newValidator}) + validator := validators.GetByPublicKey([32]byte{4}) + if validator == nil { + t.Fatal("validator not found") + } + validator.AddAccumReward(big.NewInt(90)) + candidatesS, err := candidates.NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + candidatesS.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidatesS.SetOnline([32]byte{4}) + candidatesS.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "1000000000000000000000", + BipValue: "1000000000000000000000", + }, + }, nil) + candidatesS.RecalculateStakes(0) + validators.SetNewValidators(candidatesS.GetNewCandidates(1)) + + validators.PayRewards(0) + + if accs.GetBalance([20]byte{1}, 0).String() != "72" { + t.Fatal("delegate did not receive the award") + } + if accs.GetBalance([20]byte{2}, 0).String() != "8" { + t.Fatal("rewards_address did not receive the award") + } + + if accs.GetBalance(dao.Address, 0).String() != "10" { + t.Fatal("dao_address did not receive the award") + } + if accs.GetBalance(developers.Address, 0).String() != "10" { + t.Fatal("developers_address did not receive the award") + } +} + +func TestValidators_SetValidatorAbsent(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + b.SetChecker(checker.NewChecker(b)) + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + appBus, err := app.NewApp(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetApp(appBus) + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + newValidator := NewValidator( + [32]byte{4}, + types.NewBitArray(validatorMaxAbsentWindow), + big.NewInt(1000000), + big.NewInt(100), + true, + true, + true, + b) + validators.SetValidators([]*Validator{newValidator}) + + candidatesS, err := candidates.NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + candidatesS.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidatesS.SetOnline([32]byte{4}) + candidatesS.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "1000000000000000000000", + BipValue: "1000000000000000000000", + }, + }, nil) + candidatesS.RecalculateStakes(0) + validators.SetNewValidators(candidatesS.GetNewCandidates(1)) + + validator := validators.GetByPublicKey([32]byte{4}) + if validator == nil { + t.Fatal("validator not found") + } + for i := uint64(0); i < validatorMaxAbsentTimes+1; i++ { + validators.SetValidatorAbsent(i, validator.tmAddress) + } + if !validator.IsToDrop() { + t.Fatal("validator not drop") + } +} +func TestValidators_SetValidatorPresent(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{4}, big.NewInt(1000000)) + + validator := validators.GetByPublicKey([32]byte{4}) + if validator == nil { + t.Fatal("validator not found") + } + + validators.SetValidatorAbsent(0, validator.tmAddress) + + if validator.AbsentTimes.String() != "BA{24:x_______________________}" { + t.Fatal("validator has not absent") + } + + validators.SetValidatorPresent(0, validator.tmAddress) + + if validator.AbsentTimes.String() != "BA{24:________________________}" { + t.Fatal("validator has absent") + } +} + +func TestValidators_SetToDrop(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + validators.Create([32]byte{4}, big.NewInt(1000000)) + + validator := validators.GetByPublicKey([32]byte{4}) + if validator == nil { + t.Fatal("validator not found") + } + + if validator.toDrop { + t.Fatal("default validator set to drop") + } + validators.SetToDrop([32]byte{4}) + if !validator.toDrop { + t.Fatal("validator not set to drop") + } +} + +func TestValidators_Export(t *testing.T) { + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + b := bus.NewBus() + accs, err := accounts.NewAccounts(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetAccounts(accounts.NewBus(accs)) + b.SetChecker(checker.NewChecker(b)) + b.SetEvents(eventsdb.NewEventsStore(db.NewMemDB())) + appBus, err := app.NewApp(b, mutableTree) + if err != nil { + t.Fatal(err) + } + b.SetApp(appBus) + validators, err := NewValidators(b, mutableTree) + if err != nil { + t.Fatal(err) + } + newValidator := NewValidator( + [32]byte{4}, + types.NewBitArray(validatorMaxAbsentWindow), + helpers.BipToPip(big.NewInt(1000000)), + big.NewInt(100), + true, + true, + true, + b) + validators.SetValidators([]*Validator{newValidator}) + + candidatesS, err := candidates.NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + candidatesS.Create([20]byte{1}, [20]byte{2}, [20]byte{3}, [32]byte{4}, 10) + candidatesS.SetOnline([32]byte{4}) + candidatesS.SetStakes([32]byte{4}, []types.Stake{ + { + Owner: [20]byte{1}, + Coin: 0, + Value: "1000000000000000000000", + BipValue: "1000000000000000000000", + }, + }, nil) + candidatesS.RecalculateStakes(0) + validators.SetNewValidators(candidatesS.GetNewCandidates(1)) + + err = validators.Commit() + if err != nil { + t.Fatal(err) + } + + hash, version, err := mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + if version != 1 { + t.Fatalf("version %d", version) + } + + if fmt.Sprintf("%X", hash) != "1D50F5F03FAB5D800DBF8D9254DDC68AEAC589BD30F2839A3A5B68887CE0E34C" { + t.Fatalf("hash %X", hash) + } + + state := new(types.AppState) + validators.Export(state) + + bytes, err := json.Marshal(state.Validators) + if err != nil { + t.Fatal(err) + } + + if string(bytes) != "[{\"total_bip_stake\":\"1000000000000000000000\",\"public_key\":\"Mp0400000000000000000000000000000000000000000000000000000000000000\",\"accum_reward\":\"100\",\"absent_times\":\"________________________\"}]" { + t.Log(string(bytes)) + t.Fatal("not equal JSON") + } +} diff --git a/core/state/waitlist/bus.go b/core/state/waitlist/bus.go new file mode 100644 index 000000000..7b0d858bd --- /dev/null +++ b/core/state/waitlist/bus.go @@ -0,0 +1,18 @@ +package waitlist + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" +) + +type Bus struct { + waitlist *WaitList +} + +func (b *Bus) AddToWaitList(address types.Address, pubkey types.Pubkey, coin types.CoinID, value *big.Int) { + b.waitlist.AddWaitList(address, pubkey, coin, value) +} + +func NewBus(waitlist *WaitList) *Bus { + return &Bus{waitlist: waitlist} +} diff --git a/core/state/waitlist/model.go b/core/state/waitlist/model.go new file mode 100644 index 000000000..c33d191e6 --- /dev/null +++ b/core/state/waitlist/model.go @@ -0,0 +1,27 @@ +package waitlist + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" +) + +type Item struct { + CandidateId uint32 + Coin types.CoinID + Value *big.Int +} + +type Model struct { + List []Item + + address types.Address + markDirty func(address types.Address) +} + +func (m *Model) AddToList(candidateId uint32, coin types.CoinID, value *big.Int) { + m.List = append(m.List, Item{ + CandidateId: candidateId, + Coin: coin, + Value: new(big.Int).Set(value), + }) +} diff --git a/core/state/waitlist/waitlist.go b/core/state/waitlist/waitlist.go new file mode 100644 index 000000000..eb046d9fb --- /dev/null +++ b/core/state/waitlist/waitlist.go @@ -0,0 +1,242 @@ +package waitlist + +import ( + "bytes" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "log" + "math/big" + "sort" + "sync" +) + +const mainPrefix = byte('w') + +type RWaitList interface { + Get(address types.Address, pubkey types.Pubkey, coin types.CoinID) *Item + GetByAddress(address types.Address) *Model + GetByAddressAndPubKey(address types.Address, pubkey types.Pubkey) []Item + Export(state *types.AppState) +} + +type WaitList struct { + list map[types.Address]*Model + dirty map[types.Address]interface{} + + bus *bus.Bus + iavl tree.MTree + + lock sync.RWMutex +} + +func NewWaitList(stateBus *bus.Bus, iavl tree.MTree) (*WaitList, error) { + waitlist := &WaitList{ + bus: stateBus, + iavl: iavl, + list: map[types.Address]*Model{}, + dirty: map[types.Address]interface{}{}, + } + waitlist.bus.SetWaitList(NewBus(waitlist)) + + return waitlist, nil +} + +func (wl *WaitList) Export(state *types.AppState) { + wl.iavl.Iterate(func(key []byte, value []byte) bool { + if key[0] == mainPrefix { + address := types.BytesToAddress(key[1:]) + + model := wl.GetByAddress(address) + if model != nil && len(model.List) != 0 { + for _, w := range model.List { + state.Waitlist = append(state.Waitlist, types.Waitlist{ + CandidateID: uint64(w.CandidateId), + Owner: address, + Coin: uint64(w.Coin), + Value: w.Value.String(), + }) + } + } + } + + return false + }) + + sort.SliceStable(state.Waitlist, func(i, j int) bool { + return bytes.Compare(state.Waitlist[i].Owner.Bytes(), state.Waitlist[j].Owner.Bytes()) == 1 + }) +} + +func (wl *WaitList) Commit() error { + dirty := wl.getOrderedDirty() + for _, address := range dirty { + w := wl.getFromMap(address) + + wl.lock.Lock() + delete(wl.dirty, address) + wl.lock.Unlock() + + data, err := rlp.EncodeToBytes(w) + if err != nil { + return fmt.Errorf("can't encode object at %s: %v", address.String(), err) + } + + path := append([]byte{mainPrefix}, address.Bytes()...) + wl.iavl.Set(path, data) + } + + return nil +} + +func (wl *WaitList) GetByAddress(address types.Address) *Model { + return wl.get(address) +} + +func (wl *WaitList) Get(address types.Address, pubkey types.Pubkey, coin types.CoinID) *Item { + waitlist := wl.get(address) + if waitlist == nil { + return nil + } + + candidate := wl.bus.Candidates().GetCandidate(pubkey) + if candidate == nil { + return nil + } + + for _, item := range waitlist.List { + if item.CandidateId == candidate.ID && item.Coin == coin { + return &item + } + } + + return nil +} + +func (wl *WaitList) GetByAddressAndPubKey(address types.Address, pubkey types.Pubkey) []Item { + waitlist := wl.get(address) + if waitlist == nil { + return nil + } + + candidate := wl.bus.Candidates().GetCandidate(pubkey) + if candidate == nil { + return nil + } + + var items []Item + for _, item := range waitlist.List { + if item.CandidateId == candidate.ID { + items = append(items, item) + } + } + + return items +} + +func (wl *WaitList) AddWaitList(address types.Address, pubkey types.Pubkey, coin types.CoinID, value *big.Int) { + w := wl.getOrNew(address) + + candidate := wl.bus.Candidates().GetCandidate(pubkey) + if candidate == nil { + log.Panicf("Candidate not found: %s", pubkey.String()) + } + + w.AddToList(candidate.ID, coin, value) + wl.setToMap(address, w) + w.markDirty(address) + wl.bus.Checker().AddCoin(coin, value) +} + +func (wl *WaitList) Delete(address types.Address, pubkey types.Pubkey, coin types.CoinID) { + w := wl.get(address) + if w == nil || len(w.List) == 0 { + log.Panicf("Waitlist not found for %s", address.String()) + } + + candidate := wl.bus.Candidates().GetCandidate(pubkey) + if candidate == nil { + log.Panicf("Candidate not found: %s", pubkey.String()) + } + + value := big.NewInt(0) + items := make([]Item, 0, len(w.List)-1) + for _, item := range w.List { + if item.CandidateId != candidate.ID || item.Coin != coin { + items = append(items, item) + } else { + value.Add(value, item.Value) + } + } + + w.List = items + wl.markDirty(address) + wl.setToMap(address, w) + wl.bus.Checker().AddCoin(coin, big.NewInt(0).Neg(value)) +} + +func (wl *WaitList) getOrNew(address types.Address) *Model { + w := wl.get(address) + if w == nil { + w = &Model{List: make([]Item, 0), address: address, markDirty: wl.markDirty} + wl.setToMap(address, w) + } + + return w +} + +func (wl *WaitList) get(address types.Address) *Model { + if ff := wl.getFromMap(address); ff != nil { + return ff + } + + path := append([]byte{mainPrefix}, address.Bytes()...) + _, enc := wl.iavl.Get(path) + if len(enc) == 0 { + return nil + } + + m := new(Model) + if err := rlp.DecodeBytes(enc, m); err != nil { + panic(fmt.Sprintf("failed to decode waitlists for address %s: %s", address.String(), err)) + } + + m.address = address + m.markDirty = wl.markDirty + wl.setToMap(address, m) + + return m +} + +func (wl *WaitList) getFromMap(address types.Address) *Model { + wl.lock.RLock() + defer wl.lock.RUnlock() + + return wl.list[address] +} + +func (wl *WaitList) setToMap(address types.Address, model *Model) { + wl.lock.Lock() + defer wl.lock.Unlock() + + wl.list[address] = model +} + +func (wl *WaitList) markDirty(address types.Address) { + wl.dirty[address] = struct{}{} +} + +func (wl *WaitList) getOrderedDirty() []types.Address { + keys := make([]types.Address, 0, len(wl.dirty)) + for k := range wl.dirty { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} diff --git a/core/state/waitlist/waitlist_test.go b/core/state/waitlist/waitlist_test.go new file mode 100644 index 000000000..49e517bb9 --- /dev/null +++ b/core/state/waitlist/waitlist_test.go @@ -0,0 +1,108 @@ +package waitlist + +import ( + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/candidates" + "github.com/MinterTeam/minter-go-node/core/state/checker" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/tree" + db "github.com/tendermint/tm-db" + "math/big" + "testing" +) + +func TestWaitListToGetByAddressAndPubKey(t *testing.T) { + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + + wl, err := NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidatesState, err := candidates.NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + addr, pubkey, coin, val := types.Address{0}, types.Pubkey{0}, types.GetBaseCoinID(), big.NewInt(1e18) + + candidatesState.Create(addr, addr, addr, pubkey, 10) + + wl.AddWaitList(addr, pubkey, coin, val) + if err := wl.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + items := wl.GetByAddressAndPubKey(addr, pubkey) + if len(items) != 1 { + t.Fatal("Incorrect amount of items in waitlist") + } + + if items[0].Value.Cmp(val) != 0 || items[0].Coin != coin { + t.Fatal("Invalid waitlist data") + } + + model := wl.GetByAddress(addr) + if len(model.List) != 1 { + t.Fatal("Incorrect amount of items in waitlist") + } +} + +func TestWaitListToPartialDelete(t *testing.T) { + b := bus.NewBus() + b.SetChecker(checker.NewChecker(b)) + mutableTree, _ := tree.NewMutableTree(0, db.NewMemDB(), 1024) + + wl, err := NewWaitList(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + candidatesState, err := candidates.NewCandidates(b, mutableTree) + if err != nil { + t.Fatal(err) + } + + addr, pubkey, coin, val := types.Address{0}, types.Pubkey{0}, types.GetBaseCoinID(), big.NewInt(1e18) + candidatesState.Create(addr, addr, addr, pubkey, 10) + + wl.AddWaitList(addr, pubkey, coin, val) + wl.AddWaitList(addr, pubkey, 1, val) + wl.AddWaitList(addr, pubkey, 2, val) + if err := wl.Commit(); err != nil { + t.Fatal(err) + } + + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + wl.Delete(addr, pubkey, 0) + wl.Delete(addr, pubkey, 1) + wl.AddWaitList(addr, pubkey, 1, big.NewInt(1e17)) + _, _, err = mutableTree.SaveVersion() + if err != nil { + t.Fatal(err) + } + + items := wl.GetByAddressAndPubKey(addr, pubkey) + if len(items) != 2 { + t.Fatal("Incorrect amount of items in waitlist") + } + + if items[1].Value.Cmp(big.NewInt(1e17)) != 0 || items[1].Coin != 1 { + t.Fatal("Invalid waitlist data") + } + + if items[0].Value.Cmp(val) != 0 || items[0].Coin != 2 { + t.Fatal("Invalid waitlist data") + } +} diff --git a/core/statistics/statistics.go b/core/statistics/statistics.go index 3454f6feb..092e24a0e 100644 --- a/core/statistics/statistics.go +++ b/core/statistics/statistics.go @@ -4,7 +4,7 @@ import ( "context" "github.com/prometheus/client_golang/prometheus" "github.com/tendermint/tendermint/rpc/core" - rpctypes "github.com/tendermint/tendermint/rpc/lib/types" + rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types" "net" "net/url" "runtime" @@ -77,8 +77,8 @@ type Data struct { headerTimestamp time.Time } BlockEnd blockEnd - cS chan StartRequest - cE chan EndRequest + cS chan *StartRequest + cE chan *EndRequest Speed struct { sync.RWMutex startTime time.Time @@ -170,12 +170,12 @@ func New() *Data { Api: apiResponseTime{responseTime: apiVec}, Peer: peerPing{ping: peerVec}, BlockEnd: blockEnd{HeightProm: height, DurationProm: lastBlockDuration, TimestampProm: timeBlock}, - cS: make(chan StartRequest), - cE: make(chan EndRequest), + cS: make(chan *StartRequest, 120), + cE: make(chan *EndRequest, 120), } } -func (d *Data) PushStartBlock(req StartRequest) { +func (d *Data) PushStartBlock(req *StartRequest) { if d == nil { return } @@ -199,7 +199,7 @@ func (d *Data) handleStartBlocks(ctx context.Context) { for { d.BlockStart.RLock() - ok := (height == d.BlockStart.height+1) || 0 == d.BlockStart.height + ok := (height == d.BlockStart.height+1) || d.BlockStart.height == 0 d.BlockStart.RUnlock() if ok { break @@ -218,7 +218,7 @@ func (d *Data) handleStartBlocks(ctx context.Context) { } } -func (d *Data) PushEndBlock(req EndRequest) { +func (d *Data) PushEndBlock(req *EndRequest) { if d == nil { return } @@ -295,8 +295,6 @@ func (d *Data) handleEndBlock(ctx context.Context) { d.Speed.startTime = time.Now().Add(-12 * time.Hour) d.Speed.startHeight = height - (height-d.Speed.startHeight)/2 d.Speed.duration = d.Speed.duration/2 + duration.Nanoseconds() - - return }() } } diff --git a/core/transaction/buy_coin.go b/core/transaction/buy_coin.go index 226562108..82c78a9cc 100644 --- a/core/transaction/buy_coin.go +++ b/core/transaction/buy_coin.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -14,26 +13,12 @@ import ( ) type BuyCoinData struct { - CoinToBuy types.CoinSymbol + CoinToBuy types.CoinID ValueToBuy *big.Int - CoinToSell types.CoinSymbol + CoinToSell types.CoinID MaximumValueToSell *big.Int } -func (data BuyCoinData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - CoinToBuy string `json:"coin_to_buy"` - ValueToBuy string `json:"value_to_buy"` - CoinToSell string `json:"coin_to_sell"` - MaximumValueToSell string `json:"maximum_value_to_sell"` - }{ - CoinToBuy: data.CoinToBuy.String(), - ValueToBuy: data.ValueToBuy.String(), - CoinToSell: data.CoinToSell.String(), - MaximumValueToSell: data.MaximumValueToSell.String(), - }) -} - func (data BuyCoinData) String() string { return fmt.Sprintf("BUY COIN sell:%s buy:%s %s", data.CoinToSell.String(), data.ValueToBuy.String(), data.CoinToBuy.String()) @@ -43,7 +28,7 @@ func (data BuyCoinData) Gas() int64 { return commissions.ConvertTx } -func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, +func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.CheckState) (TotalSpends, []Conversion, *big.Int, *Response) { total := TotalSpends{} var conversions []Conversion @@ -52,9 +37,9 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total commissionIncluded := false if !data.CoinToBuy.IsBaseCoin() { - coin := context.Coins.GetCoin(data.CoinToBuy) + coin := context.Coins().GetCoin(data.CoinToBuy) - if errResp := CheckForCoinSupplyOverflow(coin.Volume(), data.ValueToBuy, coin.MaxSupply()); errResp != nil { + if errResp := CheckForCoinSupplyOverflow(coin, data.ValueToBuy); errResp != nil { return nil, nil, nil, errResp } } @@ -63,7 +48,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total switch { case data.CoinToSell.IsBaseCoin(): - coin := context.Coins.GetCoin(data.CoinToBuy) + coin := context.Coins().GetCoin(data.CoinToBuy) value = formula.CalculatePurchaseAmount(coin.Volume(), coin.Reserve(), coin.Crr(), data.ValueToBuy) if value.Cmp(data.MaximumValueToSell) == 1 { @@ -72,10 +57,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total Log: fmt.Sprintf( "You wanted to sell maximum %s, but currently you need to spend %s to complete tx", data.MaximumValueToSell.String(), value.String()), - Info: EncodeError(map[string]string{ - "maximum_value_to_sell": data.MaximumValueToSell.String(), - "needed_spend_value": value.String(), - }), + Info: EncodeError(code.NewMaximumValueToSellReached(data.MaximumValueToSell.String(), value.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -96,11 +78,12 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coin.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coin.GetFullSymbol(), + coin.ID().String(), + coin.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -111,7 +94,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -130,7 +113,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total valueToBuy.Add(valueToBuy, commissionInBaseCoin) } - coin := context.Coins.GetCoin(data.CoinToSell) + coin := context.Coins().GetCoin(data.CoinToSell) value = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), valueToBuy) if value.Cmp(data.MaximumValueToSell) == 1 { @@ -139,10 +122,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total Log: fmt.Sprintf( "You wanted to sell maximum %s, but currently you need to spend %s to complete tx", data.MaximumValueToSell.String(), value.String()), - Info: EncodeError(map[string]string{ - "maximum_value_to_sell": data.MaximumValueToSell.String(), - "needed_spend_value": value.String(), - }), + Info: EncodeError(code.NewMaximumValueToSellReached(data.MaximumValueToSell.String(), value.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -156,8 +136,8 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total default: valueToBuy := big.NewInt(0).Set(data.ValueToBuy) - coinFrom := context.Coins.GetCoin(data.CoinToSell) - coinTo := context.Coins.GetCoin(data.CoinToBuy) + coinFrom := context.Coins().GetCoin(data.CoinToSell) + coinTo := context.Coins().GetCoin(data.CoinToBuy) baseCoinNeeded := formula.CalculatePurchaseAmount(coinTo.Volume(), coinTo.Reserve(), coinTo.Crr(), valueToBuy) if coinFrom.Reserve().Cmp(baseCoinNeeded) < 0 { @@ -168,11 +148,12 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total types.GetBaseCoin(), baseCoinNeeded.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coinFrom.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -193,11 +174,12 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coinTo.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coinTo.GetFullSymbol(), + coinTo.ID().String(), + coinTo.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -208,7 +190,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -219,10 +201,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total Code: code.MaximumValueToSellReached, Log: fmt.Sprintf("You wanted to sell maximum %s, but currently you need to spend %s to complete tx", data.MaximumValueToSell.String(), value.String()), - Info: EncodeError(map[string]string{ - "maximum_value_to_sell": data.MaximumValueToSell.String(), - "needed_spend_value": value.String(), - }), + Info: EncodeError(code.NewMaximumValueToSellReached(data.MaximumValueToSell.String(), value.String(), coinFrom.GetFullSymbol(), coinFrom.ID().String())), } } @@ -243,11 +222,12 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coinFrom.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -258,7 +238,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) totalValue := big.NewInt(0).Add(value, commission) @@ -266,10 +246,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total return nil, nil, nil, &Response{ Code: code.MaximumValueToSellReached, Log: fmt.Sprintf("You wanted to sell maximum %s, but currently you need to spend %s to complete tx", data.MaximumValueToSell.String(), totalValue.String()), - Info: EncodeError(map[string]string{ - "maximum_value_to_sell": data.MaximumValueToSell.String(), - "needed_spend_value": value.String(), - }), + Info: EncodeError(code.NewMaximumValueToSellReached(data.MaximumValueToSell.String(), value.String(), coinFrom.GetFullSymbol(), coinFrom.ID().String())), } } } @@ -289,7 +266,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total commission := big.NewInt(0).Set(commissionInBaseCoin) if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + coin := context.Coins().GetCoin(tx.GasCoin) if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { return nil, nil, nil, &Response{ @@ -299,11 +276,12 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coin.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coin.GetFullSymbol(), + coin.ID().String(), + coin.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -312,7 +290,7 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -322,93 +300,103 @@ func (data BuyCoinData) TotalSpend(tx *Transaction, context *state.State) (Total return total, conversions, value, nil } -func (data BuyCoinData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data BuyCoinData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.ValueToBuy == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} - } + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } - if data.CoinToSell == data.CoinToBuy { - return &Response{ - Code: code.CrossConvert, - Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin")} } - if !context.Coins.Exists(data.CoinToSell) { + if !context.Coins().Exists(data.CoinToSell) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", data.CoinToSell), - Info: EncodeError(map[string]string{ - "coin_to_sell": fmt.Sprintf("%s", data.CoinToSell), - }), + Info: EncodeError(code.NewCoinNotExists("", data.CoinToSell.String())), } } - if !context.Coins.Exists(data.CoinToBuy) { + if !context.Coins().Exists(data.CoinToBuy) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", data.CoinToBuy), - Info: EncodeError(map[string]string{ - "coin_to_buy": fmt.Sprintf("%s", data.CoinToBuy), - }), + Info: EncodeError(code.NewCoinNotExists("", data.CoinToBuy.String())), + } + } + + if data.CoinToSell == data.CoinToBuy { + return &Response{ + Code: code.CrossConvert, + Log: "\"From\" coin equals to \"to\" coin", + Info: EncodeError(code.NewCrossConvert( + data.CoinToSell.String(), + context.Coins().GetCoin(data.CoinToSell).GetFullSymbol(), + data.CoinToBuy.String(), + context.Coins().GetCoin(data.CoinToBuy).GetFullSymbol()), + ), } } return nil } -func (data BuyCoinData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data BuyCoinData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } - totalSpends, conversions, value, response := data.TotalSpend(tx, context) + totalSpends, conversions, value, response := data.TotalSpend(tx, checkState) if response != nil { return *response } for _, ts := range totalSpends { - if context.Accounts.GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + if checkState.Accounts().GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + coin := checkState.Coins().GetCoin(ts.Coin) + return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s.", sender.String(), ts.Value.String(), - ts.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": ts.Value.String(), - "coin": fmt.Sprintf("%s", ts.Coin), - }), + coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), ts.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } } - errResp := checkConversionsReserveUnderflow(conversions, context) + errResp := checkConversionsReserveUnderflow(conversions, checkState) if errResp != nil { return *errResp } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { for _, ts := range totalSpends { - context.Accounts.SubBalance(sender, ts.Coin, ts.Value) + deliverState.Accounts.SubBalance(sender, ts.Coin, ts.Value) } for _, conversion := range conversions { - context.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) - context.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) + deliverState.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) + deliverState.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) - context.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) - context.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) + deliverState.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) + deliverState.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) } rewardPool.Add(rewardPool, tx.CommissionInBaseCoin()) - context.Accounts.AddBalance(sender, data.CoinToBuy, data.ValueToBuy) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.AddBalance(sender, data.CoinToBuy, data.ValueToBuy) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ @@ -427,9 +415,9 @@ func (data BuyCoinData) Run(tx *Transaction, context *state.State, isCheck bool, } } -func checkConversionsReserveUnderflow(conversions []Conversion, context *state.State) *Response { - var totalReserveCoins []types.CoinSymbol - totalReserveSub := make(map[types.CoinSymbol]*big.Int) +func checkConversionsReserveUnderflow(conversions []Conversion, context *state.CheckState) *Response { + var totalReserveCoins []types.CoinID + totalReserveSub := make(map[types.CoinID]*big.Int) for _, conversion := range conversions { if conversion.FromCoin.IsBaseCoin() { continue @@ -444,7 +432,7 @@ func checkConversionsReserveUnderflow(conversions []Conversion, context *state.S } for _, coinSymbol := range totalReserveCoins { - errResp := CheckReserveUnderflow(context.Coins.GetCoin(coinSymbol), totalReserveSub[coinSymbol]) + errResp := CheckReserveUnderflow(context.Coins().GetCoin(coinSymbol), totalReserveSub[coinSymbol]) if errResp != nil { return errResp } diff --git a/core/transaction/buy_coin_test.go b/core/transaction/buy_coin_test.go index 6055cef6b..56d7ea55a 100644 --- a/core/transaction/buy_coin_test.go +++ b/core/transaction/buy_coin_test.go @@ -1,9 +1,14 @@ package transaction import ( - "bytes" "crypto/ecdsa" - "fmt" + "log" + "math/big" + "math/rand" + "sync" + "testing" + "time" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" @@ -11,28 +16,22 @@ import ( "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "github.com/tendermint/go-amino" - "github.com/tendermint/tm-db" - "math/big" - "math/rand" - "sync" - "testing" - "time" + db "github.com/tendermint/tm-db" ) var ( - cdc = amino.NewCodec() - rnd = rand.New(rand.NewSource(time.Now().Unix())) ) func getState() *state.State { s, err := state.NewState(0, db.NewMemDB(), nil, 1, 1) - if err != nil { panic(err) } + s.Validators.Create(types.Pubkey{}, big.NewInt(1)) + s.Candidates.Create(types.Address{}, types.Address{}, types.Address{}, types.Pubkey{}, 10) + return s } @@ -43,28 +42,66 @@ func getTestCoinSymbol() types.CoinSymbol { return coin } -func createTestCoin(stateDB *state.State) { +func createTestCoin(stateDB *state.State) types.CoinID { + volume := helpers.BipToPip(big.NewInt(100000)) + reserve := helpers.BipToPip(big.NewInt(100000)) + + id := stateDB.App.GetNextCoinID() + stateDB.Coins.Create(id, getTestCoinSymbol(), "TEST COIN", volume, 10, reserve, + big.NewInt(0).Mul(volume, big.NewInt(10)), nil) + stateDB.App.SetCoinsCount(id.Uint32()) + stateDB.Accounts.AddBalance(types.Address{}, id, volume) + + return id +} + +func createTestCoinWithOwner(stateDB *state.State, owner types.Address) types.CoinID { volume := helpers.BipToPip(big.NewInt(100000)) reserve := helpers.BipToPip(big.NewInt(100000)) - stateDB.Coins.Create(getTestCoinSymbol(), "TEST COIN", volume, 10, reserve, big.NewInt(0).Mul(volume, big.NewInt(10))) + id := stateDB.App.GetNextCoinID() + + stateDB.Coins.Create(id, getTestCoinSymbol(), "TEST COIN", volume, 10, reserve, + big.NewInt(0).Mul(volume, big.NewInt(10)), &owner) + stateDB.App.SetCoinsCount(id.Uint32()) + stateDB.Accounts.AddBalance(types.Address{}, id, volume) + + err := stateDB.Coins.Commit() + if err != nil { + log.Fatalf("failed to commit coins: %s", err) + } + + return id +} + +func checkState(t *testing.T, cState *state.State) { + if _, err := cState.Commit(); err != nil { + t.Fatal(err) + } + + exportedState := cState.Export(1) + if err := exportedState.Verify(); err != nil { + t.Fatal(err) + } } func TestBuyCoinTxBaseToCustom(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToBuyID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + initBalance := helpers.BipToPip(big.NewInt(1000000)) + cState.Accounts.AddBalance(addr, coin, initBalance) + cState.Coins.AddVolume(coin, initBalance) toBuy := helpers.BipToPip(big.NewInt(10)) maxValToSell, _ := big.NewInt(0).SetString("159374246010000000000", 10) data := BuyCoinData{ - CoinToBuy: getTestCoinSymbol(), + CoinToBuy: coinToBuyID, ValueToBuy: toBuy, CoinToSell: coin, MaximumValueToSell: maxValToSell, @@ -96,7 +133,7 @@ func TestBuyCoinTxBaseToCustom(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -108,27 +145,29 @@ func TestBuyCoinTxBaseToCustom(t *testing.T) { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) } - testBalance := cState.Accounts.GetBalance(addr, getTestCoinSymbol()) + testBalance := cState.Accounts.GetBalance(addr, coinToBuyID) if testBalance.Cmp(toBuy) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", getTestCoinSymbol(), toBuy, testBalance) } + + checkState(t, cState) } func TestBuyCoinTxInsufficientFunds(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToBuyID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) toBuy := helpers.BipToPip(big.NewInt(10)) maxValToSell, _ := big.NewInt(0).SetString("159374246010000000000", 10) data := BuyCoinData{ - CoinToBuy: getTestCoinSymbol(), + CoinToBuy: coinToBuyID, ValueToBuy: toBuy, CoinToSell: coin, MaximumValueToSell: maxValToSell, @@ -161,22 +200,26 @@ func TestBuyCoinTxInsufficientFunds(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.InsufficientFunds { t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxEqualCoins(t *testing.T) { cState := getState() + coinID := createTestCoin(cState) + privateKey, _ := crypto.GenerateKey() data := BuyCoinData{ - CoinToBuy: getTestCoinSymbol(), + CoinToBuy: coinID, ValueToBuy: big.NewInt(0), - CoinToSell: getTestCoinSymbol(), + CoinToSell: coinID, } encodedData, err := rlp.EncodeToBytes(data) @@ -189,7 +232,7 @@ func TestBuyCoinTxEqualCoins(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -205,11 +248,13 @@ func TestBuyCoinTxEqualCoins(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.CrossConvert { t.Fatalf("Response code is not %d. Error %s", code.CrossConvert, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxNotExistsBuyCoin(t *testing.T) { @@ -218,9 +263,9 @@ func TestBuyCoinTxNotExistsBuyCoin(t *testing.T) { privateKey, _ := crypto.GenerateKey() data := BuyCoinData{ - CoinToBuy: types.CoinSymbol{}, + CoinToBuy: cState.App.GetNextCoinID(), ValueToBuy: big.NewInt(0), - CoinToSell: types.GetBaseCoin(), + CoinToSell: types.GetBaseCoinID(), } encodedData, err := rlp.EncodeToBytes(data) @@ -233,7 +278,7 @@ func TestBuyCoinTxNotExistsBuyCoin(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -249,11 +294,13 @@ func TestBuyCoinTxNotExistsBuyCoin(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.CoinNotExists { t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxNotExistsSellCoin(t *testing.T) { @@ -262,9 +309,9 @@ func TestBuyCoinTxNotExistsSellCoin(t *testing.T) { privateKey, _ := crypto.GenerateKey() data := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), + CoinToBuy: types.GetBaseCoinID(), ValueToBuy: big.NewInt(0), - CoinToSell: types.CoinSymbol{}, + CoinToSell: cState.App.GetNextCoinID(), } encodedData, err := rlp.EncodeToBytes(data) @@ -277,7 +324,7 @@ func TestBuyCoinTxNotExistsSellCoin(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -293,24 +340,26 @@ func TestBuyCoinTxNotExistsSellCoin(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.CoinNotExists { t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxNotExistsGasCoin(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToSellID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() data := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), + CoinToBuy: types.GetBaseCoinID(), ValueToBuy: big.NewInt(0), - CoinToSell: getTestCoinSymbol(), + CoinToSell: coinToSellID, } encodedData, err := rlp.EncodeToBytes(data) @@ -323,7 +372,7 @@ func TestBuyCoinTxNotExistsGasCoin(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.CoinSymbol{}, + GasCoin: cState.App.GetNextCoinID(), Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -339,28 +388,32 @@ func TestBuyCoinTxNotExistsGasCoin(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.CoinNotExists { t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxNotGasCoin(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToSellID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - cState.Accounts.AddBalance(addr, getTestCoinSymbol(), helpers.BipToPip(big.NewInt(1000))) + initBal := helpers.BipToPip(big.NewInt(1000)) + cState.Accounts.AddBalance(addr, coinToSellID, initBal) + cState.Coins.AddVolume(coinToSellID, initBal) data := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), + CoinToBuy: types.GetBaseCoinID(), ValueToBuy: big.NewInt(1), - CoinToSell: getTestCoinSymbol(), - MaximumValueToSell: big.NewInt(10004502852067863), + CoinToSell: coinToSellID, + MaximumValueToSell: big.NewInt(10100004545002879), } encodedData, err := rlp.EncodeToBytes(data) @@ -373,7 +426,7 @@ func TestBuyCoinTxNotGasCoin(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: getTestCoinSymbol(), + GasCoin: coinToSellID, Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -389,52 +442,33 @@ func TestBuyCoinTxNotGasCoin(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } -} - -func TestBuyCoinTxJSON(t *testing.T) { - str := "{\"coin_to_buy\":\"%s\",\"value_to_buy\":\"1\",\"coin_to_sell\":\"TEST\",\"maximum_value_to_sell\":\"1\"}" - out := []byte(fmt.Sprintf(str, types.GetBaseCoin().String())) - - buyCoinData := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), - ValueToBuy: big.NewInt(1), - CoinToSell: getTestCoinSymbol(), - MaximumValueToSell: big.NewInt(1), - } - - result, err := cdc.MarshalJSON(buyCoinData) - - if err != nil { - t.Fatalf("Error: %s", err.Error()) - } - if !bytes.Equal(out, result) { - t.Fatalf("Error: result is not correct %s, expected %s", string(result), string(out)) - } + checkState(t, cState) } func TestBuyCoinTxCustomToBase(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToSellID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := getTestCoinSymbol() - cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(10000000))) + initBal := helpers.BipToPip(big.NewInt(10000000)) + cState.Accounts.AddBalance(addr, coinToSellID, initBal) + cState.Coins.AddVolume(coinToSellID, initBal) toBuy := helpers.BipToPip(big.NewInt(10)) maxValToSell, _ := big.NewInt(0).SetString("159374246010000000000", 10) data := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), + CoinToBuy: types.GetBaseCoinID(), ValueToBuy: toBuy, - CoinToSell: coin, + CoinToSell: coinToSellID, MaximumValueToSell: maxValToSell, } @@ -448,7 +482,7 @@ func TestBuyCoinTxCustomToBase(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: coin, + GasCoin: coinToSellID, Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -464,53 +498,56 @@ func TestBuyCoinTxCustomToBase(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } - targetBalance, _ := big.NewInt(0).SetString("9999998989954092563427063", 10) - balance := cState.Accounts.GetBalance(addr, coin) + targetBalance, _ := big.NewInt(0).SetString("9999897985363348906133281", 10) + balance := cState.Accounts.GetBalance(addr, coinToSellID) if balance.Cmp(targetBalance) != 0 { - t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coinToSellID.String(), targetBalance, balance) } - baseBalance := cState.Accounts.GetBalance(addr, types.GetBaseCoin()) + baseBalance := cState.Accounts.GetBalance(addr, types.GetBaseCoinID()) if baseBalance.Cmp(toBuy) != 0 { - t.Fatalf("Target %s balance is not correct. Expected %s, got %s", types.GetBaseCoin(), toBuy, baseBalance) + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", types.GetBaseCoinID(), toBuy, baseBalance) } - coinData := cState.Coins.GetCoin(coin) + coinData := cState.Coins.GetCoin(coinToSellID) targetReserve, _ := big.NewInt(0).SetString("99989900000000000000000", 10) if coinData.Reserve().Cmp(targetReserve) != 0 { - t.Fatalf("Target %s reserve is not correct. Expected %s, got %s", coin, targetBalance, coinData.Reserve()) + t.Fatalf("Target %s reserve is not correct. Expected %s, got %s", coinToSellID.String(), targetBalance, coinData.Reserve()) } - targetVolume, _ := big.NewInt(0).SetString("99998989954092563427063", 10) + targetVolume, _ := big.NewInt(0).SetString("10099897985363348906133281", 10) if coinData.Volume().Cmp(targetVolume) != 0 { - t.Fatalf("Target %s volume is not correct. Expected %s, got %s", coin, targetVolume, coinData.Volume()) + t.Fatalf("Target %s volume is not correct. Expected %s, got %s", coinToSellID.String(), targetVolume, coinData.Volume()) } + + checkState(t, cState) } func TestBuyCoinReserveUnderflow(t *testing.T) { cState := getState() - createTestCoin(cState) + coinToSellID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := getTestCoinSymbol() - cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(10000000))) + initBal := helpers.BipToPip(big.NewInt(10000000)) + cState.Accounts.AddBalance(addr, coinToSellID, initBal) + cState.Coins.AddVolume(coinToSellID, initBal) toBuy := helpers.BipToPip(big.NewInt(99000)) - maxValToSell, _ := big.NewInt(0).SetString("36904896537720035723223", 10) + maxValToSell, _ := big.NewInt(0).SetString("3727394550309723608045536", 10) data := BuyCoinData{ - CoinToBuy: types.GetBaseCoin(), + CoinToBuy: types.GetBaseCoinID(), ValueToBuy: toBuy, - CoinToSell: coin, + CoinToSell: coinToSellID, MaximumValueToSell: maxValToSell, } @@ -524,7 +561,7 @@ func TestBuyCoinReserveUnderflow(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: coin, + GasCoin: coinToSellID, Type: TypeBuyCoin, Data: encodedData, SignatureType: SigTypeSingle, @@ -540,11 +577,13 @@ func TestBuyCoinReserveUnderflow(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.CoinReserveUnderflow { t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) } + + checkState(t, cState) } func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { @@ -552,19 +591,21 @@ func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { // buy_coin: TEST // gas_coin: MNT - coinToSell := types.GetBaseCoin() + coinToSell := types.GetBaseCoinID() coinToBuy := types.StrToCoinSymbol("TEST") - gasCoin := types.GetBaseCoin() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + coinToBuyID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() + cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Coins.AddVolume(coinToSell, initialBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSell, coinToBuyID, gasCoin, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -575,13 +616,13 @@ func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) if buyCoinBalance.Cmp(toBuy) != 0 { t.Fatalf("Buy coin balance is not correct") } @@ -596,7 +637,7 @@ func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) @@ -609,6 +650,8 @@ func TestBuyCoinTxBaseToCustomBaseCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { @@ -617,20 +660,25 @@ func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { // gas_coin: MNT coinToSell := types.StrToCoinSymbol("TEST") - coinToBuy := types.GetBaseCoin() - gasCoin := types.GetBaseCoin() + coinToBuy := types.GetBaseCoinID() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance, _ := big.NewInt(0).SetString("100000000000000000", 10) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + coinToSellID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume.Add(initialVolume, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + cState.Coins.AddVolume(gasCoin, initialGasBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSellID, coinToBuy, gasCoin, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -641,7 +689,7 @@ func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -653,7 +701,7 @@ func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculateSaleAmount(initialVolume, initialReserve, crr, toBuy)) if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { @@ -661,7 +709,7 @@ func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Sub(estimatedReserve, toBuy) @@ -674,6 +722,8 @@ func TestBuyCoinTxCustomToBaseBaseCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { @@ -683,21 +733,24 @@ func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST12") - gasCoin := types.GetBaseCoin() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance, _ := big.NewInt(0).SetString("100000000000000000", 10) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume1.Add(initialVolume1, initialBalance) + cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSellID, coinToBuyID, gasCoin, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -708,19 +761,19 @@ func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) if buyCoinBalance.Cmp(toBuy) != 0 { t.Fatalf("Buy coin balance is not correct") } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) @@ -730,7 +783,7 @@ func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) @@ -743,6 +796,8 @@ func TestBuyCoinTxCustomToCustomBaseCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { @@ -750,21 +805,22 @@ func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { // buy_coin: TEST // gas_coin: TEST - coinToSell := types.GetBaseCoin() + coinToSell := types.GetBaseCoinID() coinToBuy := types.StrToCoinSymbol("TEST") - gasCoin := types.StrToCoinSymbol("TEST") initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + coinToBuyID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() cState.Accounts.AddBalance(addr, coinToSell, initialBalance) - cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + cState.Accounts.AddBalance(addr, coinToBuyID, initialGasBalance) + cState.Coins.AddVolume(coinToBuyID, initialGasBalance) + initialVolume.Add(initialVolume, initialGasBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSell, coinToBuyID, coinToBuyID, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -775,13 +831,13 @@ func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins + commission - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) estimatedBuyCoinBalance := big.NewInt(0).Set(toBuy) estimatedBuyCoinBalance.Add(estimatedBuyCoinBalance, initialGasBalance) toReserve := formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy) @@ -800,7 +856,7 @@ func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume, initialReserve, crr, toBuy)) @@ -815,6 +871,8 @@ func TestBuyCoinTxBaseToCustomCustomCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply. Expected %s, got %s", estimatedSupply.String(), coinData.Volume().String()) } + + checkState(t, cState) } func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { @@ -823,18 +881,19 @@ func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { // gas_coin: TEST coinToSell := types.StrToCoinSymbol("TEST") - coinToBuy := types.GetBaseCoin() - gasCoin := types.StrToCoinSymbol("TEST") + coinToBuy := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + coinToSellID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume.Add(initialVolume, initialBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSellID, coinToBuy, coinToSellID, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -845,7 +904,7 @@ func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -857,7 +916,7 @@ func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) shouldGive := formula.CalculateSaleAmount(initialVolume, initialReserve, crr, big.NewInt(0).Add(toBuy, tx.CommissionInBaseCoin())) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, shouldGive) @@ -867,7 +926,7 @@ func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Sub(estimatedReserve, toBuy) @@ -883,6 +942,8 @@ func TestBuyCoinTxCustomToBaseCustomCommission(t *testing.T) { t.Fatalf("Wrong coin supply. Expected %s, got %s", estimatedSupply.String(), coinData.Volume().String()) } } + + checkState(t, cState) } func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { @@ -892,18 +953,19 @@ func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST2") - gasCoin := types.StrToCoinSymbol("TEST1") initialBalance := helpers.BipToPip(big.NewInt(10000000)) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume1.Add(initialVolume1, initialBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSellID, coinToBuyID, coinToSellID, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -914,19 +976,19 @@ func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) if buyCoinBalance.Cmp(toBuy) != 0 { t.Fatalf("Buy coin balance is not correct") } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) @@ -939,7 +1001,7 @@ func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) @@ -957,7 +1019,7 @@ func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { } { - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve2) estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) @@ -971,6 +1033,8 @@ func TestBuyCoinTxCustomToCustomCustom1Commission(t *testing.T) { t.Fatalf("Wrong coin supply") } } + + checkState(t, cState) } func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { @@ -980,20 +1044,24 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST2") - gasCoin := types.StrToCoinSymbol("TEST2") initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toBuy := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) - cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume1.Add(initialVolume1, initialBalance) + + cState.Accounts.AddBalance(addr, coinToBuyID, initialGasBalance) + cState.Coins.AddVolume(coinToBuyID, initialGasBalance) + initialVolume2.Add(initialVolume2, initialGasBalance) - tx := createBuyCoinTx(coinToSell, coinToBuy, gasCoin, toBuy, 1) + tx := createBuyCoinTx(coinToSellID, coinToBuyID, coinToBuyID, toBuy, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -1004,13 +1072,13 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) buyCoinBalance.Sub(buyCoinBalance, initialGasBalance) toReserve := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) commission := formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume2, toBuy), big.NewInt(0).Add(initialReserve2, toReserve), crr2, tx.CommissionInBaseCoin()) @@ -1020,7 +1088,7 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) toSellBaseCoin := formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy) toSell := formula.CalculateSaleAmount(initialVolume1, initialReserve1, crr1, toSellBaseCoin) @@ -1031,7 +1099,7 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) @@ -1047,7 +1115,7 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { } { - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve2) estimatedReserve.Add(estimatedReserve, formula.CalculatePurchaseAmount(initialVolume2, initialReserve2, crr2, toBuy)) @@ -1063,9 +1131,274 @@ func TestBuyCoinTxCustomToCustomCustom2Commission(t *testing.T) { t.Fatalf("Wrong coin supply") } } + + checkState(t, cState) +} + +func TestBuyCoinTxToCoinSupplyOverflow(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, sellCoinID, helpers.BipToPip(big.NewInt(5000000))) + + tx := createBuyCoinTx(sellCoinID, coinToBuyID, sellCoinID, helpers.BipToPip(big.NewInt(1000001)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinSupplyOverflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinSupplyOverflow, response.Log) + } + + checkState(t, cState) +} + +func TestBuyCoinTxToMaximumValueToSellReached(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + valueToBuy := big.NewInt(2e18) + //cState.Accounts.AddBalance(addr, sellCoinID, valueToBuy) + cState.Coins.AddVolume(sellCoinID, valueToBuy) + + data := BuyCoinData{ + CoinToBuy: coinToBuyID, + ValueToBuy: valueToBuy, + CoinToSell: sellCoinID, + MaximumValueToSell: big.NewInt(1e18), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx := &Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: sellCoinID, + Type: TypeBuyCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + decodedData: data, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MaximumValueToSellReached { + t.Fatalf("Response code is not %d. Error %s", code.MaximumValueToSellReached, response.Log) + } + + cState.Accounts.AddBalance(addr, coinToBuyID, helpers.BipToPip(big.NewInt(100000))) + cState.Coins.AddVolume(coinToBuyID, helpers.BipToPip(big.NewInt(100000))) + + data.CoinToBuy = sellCoinID + data.CoinToSell = coinToBuyID + data.MaximumValueToSell = big.NewInt(1) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MaximumValueToSellReached { + t.Fatalf("Response code is not %d. Error %s", code.MaximumValueToSellReached, response.Log) + } + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + cState.Accounts.AddBalance(types.Address{0}, coinToSellID, helpers.BipToPip(big.NewInt(100000))) + + data.CoinToBuy = coinToBuyID + data.CoinToSell = coinToSellID + data.MaximumValueToSell = big.NewInt(1) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MaximumValueToSellReached { + t.Fatalf("Response code is not %d. Error %s", code.MaximumValueToSellReached, response.Log) + } + + checkState(t, cState) + + data.MaximumValueToSell = big.NewInt(1000360064812986923) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + tx.GasCoin = data.CoinToSell + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MaximumValueToSellReached { + t.Fatalf("Response code is not %d. Error %s", code.MaximumValueToSellReached, response.Log) + } + + checkState(t, cState) +} + +func TestBuyCoinTxToCoinReserveNotSufficient(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID := createTestCoin(cState) + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(5000000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(10000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + cState.Accounts.AddBalance(addr, coinToSellID, helpers.BipToPip(big.NewInt(5000000))) + + tx := createBuyCoinTx(coinToSellID, coinToBuyID, coinToBuyID, helpers.BipToPip(big.NewInt(10000)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) + + // gas coin == coin to buy + + cState.Coins.SubReserve(tx.GasCoin, helpers.BipToPip(big.NewInt(100000))) + + tx = createBuyCoinTx(coinToSellID, coinToBuyID, coinToBuyID, helpers.BipToPip(big.NewInt(1)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) + + // gas coin == coin to sell + + tx = createBuyCoinTx(coinToBuyID, coinToSellID, coinToBuyID, helpers.BipToPip(big.NewInt(1)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) + + // gas coin == coin to buy + // sell coin == base coin + + tx = createBuyCoinTx(types.GetBaseCoinID(), coinToBuyID, coinToBuyID, big.NewInt(1), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + tx.GasPrice = 5000 + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) } -func createBuyCoinTx(sellCoin, buyCoin, gasCoin types.CoinSymbol, valueToBuy *big.Int, nonce uint64) *Transaction { +func createBuyCoinTx(sellCoin, buyCoin, gasCoin types.CoinID, valueToBuy *big.Int, nonce uint64) *Transaction { maxValToSell, _ := big.NewInt(0).SetString("100000000000000000000000000000", 10) data := BuyCoinData{ CoinToBuy: buyCoin, @@ -1100,15 +1433,18 @@ func getAccount() (*ecdsa.PrivateKey, types.Address) { return privateKey, addr } -func createTestCoinWithSymbol(stateDB *state.State, symbol types.CoinSymbol) (*big.Int, *big.Int, uint) { +func createTestCoinWithSymbol(stateDB *state.State, symbol types.CoinSymbol) (types.CoinID, *big.Int, *big.Int, uint32) { volume := helpers.BipToPip(big.NewInt(100000)) reserve := helpers.BipToPip(big.NewInt(100000)) volume.Mul(volume, big.NewInt(int64(rnd.Intn(9))+1)) reserve.Mul(reserve, big.NewInt(int64(rnd.Intn(9))+1)) - crr := uint(10 + rnd.Intn(90)) + crr := uint32(10 + rnd.Intn(90)) - stateDB.Coins.Create(symbol, "TEST COIN", volume, crr, reserve, big.NewInt(0).Mul(volume, big.NewInt(10))) + id := stateDB.App.GetNextCoinID() + stateDB.Coins.Create(id, symbol, "TEST COIN", volume, crr, reserve, big.NewInt(0).Mul(volume, big.NewInt(10)), nil) + stateDB.App.SetCoinsCount(id.Uint32()) + stateDB.Accounts.AddBalance(types.Address{}, id, volume) - return big.NewInt(0).Set(volume), big.NewInt(0).Set(reserve), crr + return id, big.NewInt(0).Set(volume), big.NewInt(0).Set(reserve), crr } diff --git a/core/transaction/create_coin.go b/core/transaction/create_coin.go index 629a2e6a8..e483d755f 100644 --- a/core/transaction/create_coin.go +++ b/core/transaction/create_coin.go @@ -2,26 +2,27 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" + "math/big" + "regexp" + "strconv" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/tendermint/tendermint/libs/kv" - "math/big" - "regexp" - "strconv" ) const maxCoinNameBytes = 64 const allowedCoinSymbols = "^[A-Z0-9]{3,10}$" var ( - minCoinSupply = helpers.BipToPip(big.NewInt(1)) - minCoinReserve = helpers.BipToPip(big.NewInt(10000)) - maxCoinSupply = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(15+18), nil) + minCoinSupply = helpers.BipToPip(big.NewInt(1)) + minCoinReserve = helpers.BipToPip(big.NewInt(10000)) + maxCoinSupply = big.NewInt(0).Exp(big.NewInt(10), big.NewInt(15+18), nil) + allowedCoinSymbolsRegexpCompile, _ = regexp.Compile(allowedCoinSymbols) ) type CreateCoinData struct { @@ -29,79 +30,73 @@ type CreateCoinData struct { Symbol types.CoinSymbol InitialAmount *big.Int InitialReserve *big.Int - ConstantReserveRatio uint + ConstantReserveRatio uint32 MaxSupply *big.Int } -func (data CreateCoinData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - InitialAmount string `json:"initial_amount"` - InitialReserve string `json:"initial_reserve"` - ConstantReserveRatio string `json:"constant_reserve_ratio"` - MaxSupply string `json:"max_supply"` - }{ - Name: data.Name, - Symbol: data.Symbol.String(), - InitialAmount: data.InitialAmount.String(), - InitialReserve: data.InitialReserve.String(), - ConstantReserveRatio: strconv.Itoa(int(data.ConstantReserveRatio)), - MaxSupply: data.MaxSupply.String(), - }) -} - -func (data CreateCoinData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data CreateCoinData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data CreateCoinData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.InitialReserve == nil || data.InitialAmount == nil || data.MaxSupply == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } if len(data.Name) > maxCoinNameBytes { return &Response{ Code: code.InvalidCoinName, - Log: fmt.Sprintf("Coin name is invalid. Allowed up to %d bytes.", maxCoinNameBytes)} + Log: fmt.Sprintf("Coin name is invalid. Allowed up to %d bytes.", maxCoinNameBytes), + Info: EncodeError(code.NewInvalidCoinName(strconv.Itoa(maxCoinNameBytes), strconv.Itoa(len(data.Name)))), + } } - if match, _ := regexp.MatchString(allowedCoinSymbols, data.Symbol.String()); !match { + if match := allowedCoinSymbolsRegexpCompile.MatchString(data.Symbol.String()); !match { return &Response{ Code: code.InvalidCoinSymbol, - Log: fmt.Sprintf("Invalid coin symbol. Should be %s", allowedCoinSymbols)} + Log: fmt.Sprintf("Invalid coin symbol. Should be %s", allowedCoinSymbols), + Info: EncodeError(code.NewInvalidCoinSymbol(allowedCoinSymbols, data.Symbol.String())), + } } - if context.Coins.Exists(data.Symbol) { + if context.Coins().ExistsBySymbol(data.Symbol) { return &Response{ Code: code.CoinAlreadyExists, - Log: fmt.Sprintf("Coin already exists")} + Log: "Coin already exists", + Info: EncodeError(code.NewCoinAlreadyExists(types.StrToCoinSymbol(data.Symbol.String()).String(), context.Coins().GetCoinBySymbol(data.Symbol, 0).ID().String())), + } } if data.ConstantReserveRatio < 10 || data.ConstantReserveRatio > 100 { return &Response{ Code: code.WrongCrr, - Log: fmt.Sprintf("Constant Reserve Ratio should be between 10 and 100")} + Log: "Constant Reserve Ratio should be between 10 and 100", + Info: EncodeError(code.NewWrongCrr("10", "100", strconv.Itoa(int(data.ConstantReserveRatio)))), + } } - if data.InitialAmount.Cmp(minCoinSupply) == -1 || data.InitialAmount.Cmp(data.MaxSupply) == 1 { + if data.MaxSupply.Cmp(maxCoinSupply) == 1 { return &Response{ Code: code.WrongCoinSupply, - Log: fmt.Sprintf("Coin supply should be between %s and %s", minCoinSupply.String(), data.MaxSupply.String())} + Log: fmt.Sprintf("Max coin supply should be less than %s", maxCoinSupply), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } } - if data.MaxSupply.Cmp(maxCoinSupply) == 1 { + if data.InitialAmount.Cmp(minCoinSupply) == -1 || data.InitialAmount.Cmp(data.MaxSupply) == 1 { return &Response{ Code: code.WrongCoinSupply, - Log: fmt.Sprintf("Max coin supply should be less than %s", maxCoinSupply)} + Log: fmt.Sprintf("Coin supply should be between %s and %s", minCoinSupply.String(), data.MaxSupply.String()), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } } if data.InitialReserve.Cmp(minCoinReserve) == -1 { return &Response{ Code: code.WrongCoinSupply, - Log: fmt.Sprintf("Coin reserve should be greater than or equal to %s", minCoinReserve.String())} + Log: fmt.Sprintf("Coin reserve should be greater than or equal to %s", minCoinReserve.String()), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } } return nil @@ -127,10 +122,15 @@ func (data CreateCoinData) Gas() int64 { return 100000 // 100 bips } -func (data CreateCoinData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data CreateCoinData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -138,50 +138,32 @@ func (data CreateCoinData) Run(tx *Transaction, context *state.State, isCheck bo commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if tx.GasCoin != types.GetBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + if tx.GasCoin != types.GetBaseCoinID() { + coin := checkState.Coins().GetCoin(tx.GasCoin) errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Gas coin reserve balance is not sufficient for transaction. Has: %s %s, required %s %s", coin.Reserve().String(), types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coin.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), - } - } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if context.Accounts.GetBalance(sender, types.GetBaseCoin()).Cmp(data.InitialReserve) < 0 { + if checkState.Accounts().GetBalance(sender, types.GetBaseCoinID()).Cmp(data.InitialReserve) < 0 { return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.InitialReserve.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_reserve": data.InitialReserve.String(), - "base_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), data.InitialReserve.String(), types.GetBaseCoin().String(), types.GetBaseCoinID().String())), } } @@ -190,36 +172,48 @@ func (data CreateCoinData) Run(tx *Transaction, context *state.State, isCheck bo totalTxCost.Add(totalTxCost, data.InitialReserve) totalTxCost.Add(totalTxCost, commission) - if context.Accounts.GetBalance(sender, types.GetBaseCoin()).Cmp(totalTxCost) < 0 { + if checkState.Accounts().GetBalance(sender, types.GetBaseCoinID()).Cmp(totalTxCost) < 0 { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": totalTxCost.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } } - if !isCheck { + var coinId = checkState.App().GetNextCoinID() + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, types.GetBaseCoinID(), data.InitialReserve) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + + deliverState.Coins.Create( + coinId, + data.Symbol, + data.Name, + data.InitialAmount, + data.ConstantReserveRatio, + data.InitialReserve, + data.MaxSupply, + &sender, + ) - context.Accounts.SubBalance(sender, types.GetBaseCoin(), data.InitialReserve) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Coins.Create(data.Symbol, data.Name, data.InitialAmount, data.ConstantReserveRatio, data.InitialReserve, data.MaxSupply) - context.Accounts.AddBalance(sender, data.Symbol, data.InitialAmount) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.App.SetCoinsCount(coinId.Uint32()) + deliverState.Accounts.AddBalance(sender, coinId, data.InitialAmount) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeCreateCoin)}))}, kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, - kv.Pair{Key: []byte("tx.coin"), Value: []byte(data.Symbol.String())}, + kv.Pair{Key: []byte("tx.coin_symbol"), Value: []byte(data.Symbol.String())}, + kv.Pair{Key: []byte("tx.coin_id"), Value: []byte(coinId.String())}, } return Response{ diff --git a/core/transaction/create_coin_test.go b/core/transaction/create_coin_test.go index dc1249d1d..fd042ee0a 100644 --- a/core/transaction/create_coin_test.go +++ b/core/transaction/create_coin_test.go @@ -1,13 +1,16 @@ package transaction import ( + "encoding/binary" + "math/big" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" ) func TestCreateCoinTx(t *testing.T) { @@ -15,16 +18,14 @@ func TestCreateCoinTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) - var toCreate types.CoinSymbol - copy(toCreate[:], []byte("ABCDEF")) - + toCreate := types.StrToCoinSymbol("ABCDEF") reserve := helpers.BipToPip(big.NewInt(10000)) amount := helpers.BipToPip(big.NewInt(100)) - crr := uint(50) + crr := uint32(50) name := "My Test Coin" data := CreateCoinData{ @@ -62,19 +63,25 @@ func TestCreateCoinTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) - + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } + err = cState.Coins.Commit() + if err != nil { + t.Fatalf("Commit coins failed. Error %s", err) + } + + checkState(t, cState) + targetBalance, _ := big.NewInt(0).SetString("989000000000000000000000", 10) balance := cState.Accounts.GetBalance(addr, coin) if balance.Cmp(targetBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) } - stateCoin := cState.Coins.GetCoin(toCreate) + stateCoin := cState.Coins.GetCoinBySymbol(toCreate, 0) if stateCoin == nil { t.Fatalf("Coin %s not found in state", toCreate) @@ -95,4 +102,742 @@ func TestCreateCoinTx(t *testing.T) { if stateCoin.Name() != name { t.Fatalf("Name in state is not correct. Expected %s, got %s", name, stateCoin.Name()) } + + if stateCoin.Version() != 0 { + t.Fatalf("Version in state is not correct. Expected %d, got %d", 0, stateCoin.Version()) + } + + symbolInfo := cState.Coins.GetSymbolInfo(toCreate) + if symbolInfo == nil { + t.Fatalf("Symbol %s info not found in state", toCreate) + } + + if *symbolInfo.OwnerAddress() != addr { + t.Fatalf("Target owner address is not correct. Expected %s, got %s", addr.String(), symbolInfo.OwnerAddress().String()) + } + + checkState(t, cState) +} + +func TestCreateCoinWithIncorrectName(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + + var name [65]byte + binary.BigEndian.PutUint64(name[:], 0) + + data := CreateCoinData{ + Name: string(name[:]), + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InvalidCoinName { + t.Fatalf("Response code is not %d. Error %s", code.InvalidCoinName, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinWithInvalidSymbol(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABC-DEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InvalidCoinSymbol { + t.Fatalf("Response code is not %d. Error %s", code.InvalidCoinSymbol, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinWithExistingSymbol(t *testing.T) { + cState := getState() + + createTestCoin(cState) + cState.Commit() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("TEST") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinAlreadyExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinAlreadyExists, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinWithWrongCrr(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(9) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCrr { + t.Fatalf("Response code is not %d. Error %s", code.WrongCrr, response.Log) + } + + checkState(t, cState) + + data.ConstantReserveRatio = uint32(101) + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx = Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCrr { + t.Fatalf("Response code is not %d. Error %s", code.WrongCrr, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinWithWrongCoinSupply(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: big.NewInt(1), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100000)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) + + data.InitialAmount = helpers.BipToPip(big.NewInt(1000000)) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx = Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) + + data.MaxSupply = big.NewInt(0).Exp(big.NewInt(100), big.NewInt(15+18), nil) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + data.MaxSupply = maxCoinSupply + data.InitialReserve = helpers.BipToPip(big.NewInt(1000)) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinGas(t *testing.T) { + data := CreateCoinData{ + Symbol: types.StrToCoinSymbol("ABC"), + } + + if data.Gas() != 1000000000 { + t.Fatal("Gas for symbol with length 3 is not correct.") + } + + data.Symbol = types.StrToCoinSymbol("ABCD") + if data.Gas() != 100000000 { + t.Fatal("Gas for symbol with length 4 is not correct.") + } + + data.Symbol = types.StrToCoinSymbol("ABCDE") + if data.Gas() != 10000000 { + t.Fatal("Gas for symbol with length 5 is not correct.") + } + + data.Symbol = types.StrToCoinSymbol("ABCDEF") + if data.Gas() != 1000000 { + t.Fatal("Gas for symbol with length 6 is not correct.") + } + + data.Symbol = types.StrToCoinSymbol("ABCDEFG") + if data.Gas() != 100000 { + t.Fatal("Gas for symbol with length 7 is not correct.") + } +} + +func TestCreateCoinWithInsufficientFundsForGas(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + + customCoin := createTestCoin(cState) + cState.Coins.SubReserve(customCoin, helpers.BipToPip(big.NewInt(90000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Coins.AddVolume(customCoin, helpers.BipToPip(big.NewInt(1050))) + cState.Accounts.AddBalance(addr, customCoin, helpers.BipToPip(big.NewInt(1050))) + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10000))) + cState.Commit() + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: customCoin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinToInsufficientFundsForGasCoin(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10000))) + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinToInsufficientFundsForInitialReserve(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(11000)) + crr := uint32(50) + name := "My Test Coin" + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10000))) + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestCreateCoinToSameSymbolInOneBlock(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr2, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("TEST") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + name := "My Test Coin" + + data := CreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not success. Error %s", response.Log) + } + + checkState(t, cState) + + if err := tx.Sign(privateKey2); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinAlreadyExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinAlreadyExists, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/create_multisig.go b/core/transaction/create_multisig.go index ef6d08668..4b8da6b5c 100644 --- a/core/transaction/create_multisig.go +++ b/core/transaction/create_multisig.go @@ -2,8 +2,10 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" + "math/big" + "strconv" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" "github.com/MinterTeam/minter-go-node/core/state" @@ -11,62 +13,39 @@ import ( "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/formula" "github.com/tendermint/tendermint/libs/kv" - "math/big" - "strconv" ) type CreateMultisigData struct { - Threshold uint - Weights []uint + Threshold uint32 + Weights []uint32 Addresses []types.Address } -func (data CreateMultisigData) MarshalJSON() ([]byte, error) { - var weights []string - for _, weight := range data.Weights { - weights = append(weights, strconv.Itoa(int(weight))) - } - - return json.Marshal(struct { - Threshold string `json:"threshold"` - Weights []string `json:"weights"` - Addresses []types.Address `json:"addresses"` - }{ - Threshold: strconv.Itoa(int(data.Threshold)), - Weights: weights, - Addresses: data.Addresses, - }) -} - -func (data CreateMultisigData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data CreateMultisigData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data CreateMultisigData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { lenWeights := len(data.Weights) if lenWeights > 32 { return &Response{ Code: code.TooLargeOwnersList, - Log: fmt.Sprintf("Owners list is limited to 32 items")} + Log: "Owners list is limited to 32 items", + Info: EncodeError(code.NewTooLargeOwnersList(strconv.Itoa(lenWeights), "32"))} } lenAddresses := len(data.Addresses) if lenAddresses != lenWeights { return &Response{ - Code: code.IncorrectWeights, - Log: fmt.Sprintf("Incorrect multisig weights"), - Info: EncodeError(map[string]string{ - "count_weights": fmt.Sprintf("%d", lenWeights), - "count_addresses": fmt.Sprintf("%d", lenAddresses), - }), + Code: code.DifferentCountAddressesAndWeights, + Log: "Different count addresses and weights", + Info: EncodeError(code.NewDifferentCountAddressesAndWeights(fmt.Sprintf("%d", lenAddresses), fmt.Sprintf("%d", lenWeights))), } } - for _, weight := range data.Weights { + for i, weight := range data.Weights { if weight > 1023 { return &Response{ Code: code.IncorrectWeights, - Log: fmt.Sprintf("Incorrect multisig weights")} + Log: "Incorrect multisig weights", + Info: EncodeError(code.NewIncorrectWeights(data.Addresses[i].String(), strconv.Itoa(int(weight)), "1024")), + } } } @@ -75,7 +54,9 @@ func (data CreateMultisigData) BasicCheck(tx *Transaction, context *state.State) if usedAddresses[address] { return &Response{ Code: code.DuplicatedAddresses, - Log: fmt.Sprintf("Duplicated multisig addresses")} + Log: "Duplicated multisig addresses", + Info: EncodeError(code.NewDuplicatedAddresses(address.String())), + } } usedAddresses[address] = true @@ -85,17 +66,23 @@ func (data CreateMultisigData) BasicCheck(tx *Transaction, context *state.State) } func (data CreateMultisigData) String() string { - return fmt.Sprintf("CREATE MULTISIG") + return "CREATE MULTISIG" } func (data CreateMultisigData) Gas() int64 { return commissions.CreateMultisig } -func (data CreateMultisigData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data CreateMultisigData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -103,67 +90,45 @@ func (data CreateMultisigData) Run(tx *Transaction, context *state.State, isChec commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - msigAddress := (&accounts.Multisig{ - Weights: data.Weights, - Threshold: data.Threshold, - Addresses: data.Addresses, - }).Address() + msigAddress := accounts.CreateMultisigAddress(sender, tx.Nonce) - if context.Accounts.ExistsMultisig(msigAddress) { + if checkState.Accounts().ExistsMultisig(msigAddress) { return Response{ Code: code.MultisigExists, Log: fmt.Sprintf("Multisig %s already exists", msigAddress.String()), - Info: EncodeError(map[string]string{ - "multisig_address": msigAddress.String(), - }), + Info: EncodeError(code.NewMultisigExists(msigAddress.String())), } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Accounts.SetNonce(sender, tx.Nonce) - context.Accounts.CreateMultisig(data.Weights, data.Addresses, data.Threshold, currentBlock) + deliverState.Accounts.CreateMultisig(data.Weights, data.Addresses, data.Threshold, msigAddress) } tags := kv.Pairs{ diff --git a/core/transaction/create_multisig_test.go b/core/transaction/create_multisig_test.go index b00c135b4..645d26866 100644 --- a/core/transaction/create_multisig_test.go +++ b/core/transaction/create_multisig_test.go @@ -1,16 +1,18 @@ package transaction import ( + "math/big" + "reflect" + "sync" + "testing" + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "reflect" - "sync" - "testing" ) func TestCreateMultisigTx(t *testing.T) { @@ -22,7 +24,7 @@ func TestCreateMultisigTx(t *testing.T) { privateKey2, _ := crypto.GenerateKey() addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -31,7 +33,7 @@ func TestCreateMultisigTx(t *testing.T) { addr2, } - weights := []uint{1, 1} + weights := []uint32{1, 1} data := CreateMultisigData{ Threshold: 1, @@ -65,7 +67,7 @@ func TestCreateMultisigTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -104,6 +106,8 @@ func TestCreateMultisigTx(t *testing.T) { if msigData.Threshold != 1 { t.Fatalf("Threshold is not correct") } + + checkState(t, cState) } func TestCreateMultisigFromExistingAccountTx(t *testing.T) { @@ -115,7 +119,7 @@ func TestCreateMultisigFromExistingAccountTx(t *testing.T) { privateKey2, _ := crypto.GenerateKey() addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -124,7 +128,7 @@ func TestCreateMultisigFromExistingAccountTx(t *testing.T) { addr2, } - weights := []uint{1, 1} + weights := []uint32{1, 1} data := CreateMultisigData{ Threshold: 1, @@ -132,14 +136,10 @@ func TestCreateMultisigFromExistingAccountTx(t *testing.T) { Addresses: addresses, } - msigAddr := (&accounts.Multisig{ - Threshold: data.Threshold, - Weights: data.Weights, - Addresses: data.Addresses, - }).Address() + msigAddr := accounts.CreateMultisigAddress(addr, 1) initialBalance := big.NewInt(10) - cState.Accounts.AddBalance(msigAddr, types.GetBaseCoin(), initialBalance) + cState.Accounts.AddBalance(msigAddr, types.GetBaseCoinID(), initialBalance) encodedData, err := rlp.EncodeToBytes(data) if err != nil { @@ -166,7 +166,7 @@ func TestCreateMultisigFromExistingAccountTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -206,9 +206,11 @@ func TestCreateMultisigFromExistingAccountTx(t *testing.T) { t.Fatalf("Threshold is not correct") } - if cState.Accounts.GetBalance(msigAddr, types.GetBaseCoin()).Cmp(initialBalance) != 0 { + if cState.Accounts.GetBalance(msigAddr, types.GetBaseCoinID()).Cmp(initialBalance) != 0 { t.Fatalf("Msig balance was not persisted") } + + checkState(t, cState) } func TestCreateExistingMultisigTx(t *testing.T) { @@ -220,20 +222,20 @@ func TestCreateExistingMultisigTx(t *testing.T) { privateKey2, _ := crypto.GenerateKey() addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) data := CreateMultisigData{ Threshold: 1, - Weights: []uint{1, 1}, + Weights: []uint32{1, 1}, Addresses: []types.Address{ addr, addr2, }, } - cState.Accounts.CreateMultisig(data.Weights, data.Addresses, data.Threshold, 1) + cState.Accounts.CreateMultisig(data.Weights, data.Addresses, data.Threshold, accounts.CreateMultisigAddress(addr, 1)) encodedData, err := rlp.EncodeToBytes(data) if err != nil { @@ -260,9 +262,330 @@ func TestCreateExistingMultisigTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.MultisigExists { t.Fatalf("Response code is not %d. Got %d", code.MultisigExists, response.Code) } + + checkState(t, cState) +} + +func TestCreateMultisigOwnersTxToNonExistAddress(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{2, 1, 2}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + response := data.BasicCheck(&tx, state.NewCheckState(cState)) + if response.Code != code.MultisigNotExists { + t.Fatalf("Response code is not %d. Error %s", code.MultisigNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestCreateMultisigOwnersTxToTooLargeOwnersList(t *testing.T) { + cState := getState() + + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := types.GetBaseCoinID() + + weights := make([]uint32, 33) + for i := uint32(0); i <= 32; i++ { + weights[i] = i + } + + data := CreateMultisigData{ + Threshold: 3, + Weights: weights, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeCreateMultisig, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.TooLargeOwnersList { + t.Fatalf("Response code is not %d. Error %s", code.TooLargeOwnersList, response.Log) + } + + checkState(t, cState) +} + +func TestCreateMultisigOwnersTxIncorrectWeights(t *testing.T) { + cState := getState() + + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + data := CreateMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3, 4}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeCreateMultisig, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.DifferentCountAddressesAndWeights { + t.Fatalf("Response code is not %d. Error %s", code.DifferentCountAddressesAndWeights, response.Log) + } + + checkState(t, cState) + + data.Weights = []uint32{1, 2, 1024} + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IncorrectWeights { + t.Fatalf("Response code is not %d. Error %s", code.IncorrectWeights, response.Log) + } + + checkState(t, cState) +} + +func TestCreateMultisigOwnersTxToAddressDuplication(t *testing.T) { + cState := getState() + + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + data := CreateMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr1, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeCreateMultisig, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.DuplicatedAddresses { + t.Fatalf("Response code is not %d. Error %s", code.DuplicatedAddresses, response.Log) + } + + checkState(t, cState) +} + +func TestCreateMultisigOwnersTxToInsufficientFunds(t *testing.T) { + cState := getState() + + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + data := CreateMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeCreateMultisig, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestCreateMultisigTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + customCoin := createTestCoin(cState) + cState.Coins.SubReserve(customCoin, helpers.BipToPip(big.NewInt(90000))) + + cState.Accounts.AddBalance(addr3, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(1000000))) + + data := CreateMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: customCoin, + Type: TypeCreateMultisig, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/declare_candidacy.go b/core/transaction/declare_candidacy.go index a77efa5c6..bdbbde914 100644 --- a/core/transaction/declare_candidacy.go +++ b/core/transaction/declare_candidacy.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -12,7 +11,6 @@ import ( "github.com/MinterTeam/minter-go-node/formula" "github.com/tendermint/tendermint/libs/kv" "math/big" - "strconv" ) const minCommission = 0 @@ -21,65 +19,49 @@ const maxCommission = 100 type DeclareCandidacyData struct { Address types.Address PubKey types.Pubkey - Commission uint - Coin types.CoinSymbol + Commission uint32 + Coin types.CoinID Stake *big.Int } -func (data DeclareCandidacyData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Address string `json:"address"` - PubKey string `json:"pub_key"` - Commission string `json:"commission"` - Coin string `json:"coin"` - Stake string `json:"stake"` - }{ - Address: data.Address.String(), - PubKey: data.PubKey.String(), - Commission: strconv.Itoa(int(data.Commission)), - Coin: data.Coin.String(), - Stake: data.Stake.String(), - }) -} - -func (data DeclareCandidacyData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data DeclareCandidacyData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data DeclareCandidacyData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.Stake == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } - if !context.Coins.Exists(data.Coin) { + if !context.Coins().Exists(data.Coin) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", data.Coin), - Info: EncodeError(map[string]string{ - "coin": fmt.Sprintf("%s", data.Coin), - }), + Info: EncodeError(code.NewCoinNotExists("", data.Coin.String())), } } - if context.Candidates.Exists(data.PubKey) { + if context.Candidates().Exists(data.PubKey) { return &Response{ Code: code.CandidateExists, Log: fmt.Sprintf("Candidate with such public key (%s) already exists", data.PubKey.String()), - Info: EncodeError(map[string]string{ - "public_key": data.PubKey.String(), - }), + Info: EncodeError(code.NewCandidateExists(data.PubKey.String())), + } + } + + if context.Candidates().IsBlockedPubKey(data.PubKey) { + return &Response{ + Code: code.PublicKeyInBlockList, + Log: fmt.Sprintf("Candidate with such public key (%s) exists in block list", data.PubKey.String()), + Info: EncodeError(code.NewPublicKeyInBlockList(data.PubKey.String())), } } if data.Commission < minCommission || data.Commission > maxCommission { return &Response{ Code: code.WrongCommission, - Log: fmt.Sprintf("Commission should be between 0 and 100"), - Info: EncodeError(map[string]string{ - "got_commission": fmt.Sprintf("%d", data.Commission), - }), + Log: "Commission should be between 0 and 100", + Info: EncodeError(code.NewWrongCommission(fmt.Sprintf("%d", data.Commission), "0", "100")), } } @@ -95,70 +77,58 @@ func (data DeclareCandidacyData) Gas() int64 { return commissions.DeclareCandidacyTx } -func (data DeclareCandidacyData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data DeclareCandidacyData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } maxCandidatesCount := validators.GetCandidatesCountForBlock(currentBlock) - if context.Candidates.Count() >= maxCandidatesCount && !context.Candidates.IsNewCandidateStakeSufficient(data.Coin, data.Stake, maxCandidatesCount) { + if checkState.Candidates().Count() >= maxCandidatesCount && !checkState.Candidates().IsNewCandidateStakeSufficient(data.Coin, data.Stake, maxCandidatesCount) { return Response{ Code: code.TooLowStake, - Log: fmt.Sprintf("Given stake is too low")} + Log: "Given stake is too low", + Info: EncodeError(code.NewTooLowStake(sender.String(), data.PubKey.String(), data.Stake.String(), data.Coin.String(), checkState.Coins().GetCoin(data.Coin).GetFullSymbol())), + } } - commissionInBaseCoin := big.NewInt(0).Mul(big.NewInt(int64(tx.GasPrice)), big.NewInt(tx.Gas())) - commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) + commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + coin := checkState.Coins().GetCoin(data.Coin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, data.Coin).Cmp(data.Stake) < 0 { + if checkState.Accounts().GetBalance(sender, data.Coin).Cmp(data.Stake) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Stake, data.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": data.Stake.String(), - "coin": fmt.Sprintf("%s", data.Coin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Stake, coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), data.Stake.String(), coin.GetFullSymbol(), coin.ID().String())), } } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } @@ -167,30 +137,26 @@ func (data DeclareCandidacyData) Run(tx *Transaction, context *state.State, isCh totalTxCost.Add(totalTxCost, data.Stake) totalTxCost.Add(totalTxCost, commission) - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": totalTxCost.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) - context.Accounts.SubBalance(sender, data.Coin, data.Stake) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Candidates.Create(data.Address, sender, data.PubKey, data.Commission) - context.Candidates.Delegate(sender, data.PubKey, data.Coin, data.Stake, big.NewInt(0)) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(sender, data.Coin, data.Stake) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Candidates.Create(data.Address, sender, sender, data.PubKey, data.Commission) + deliverState.Candidates.Delegate(sender, data.PubKey, data.Coin, data.Stake, big.NewInt(0)) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ diff --git a/core/transaction/declare_candidacy_test.go b/core/transaction/declare_candidacy_test.go index f522db515..1373d8a51 100644 --- a/core/transaction/declare_candidacy_test.go +++ b/core/transaction/declare_candidacy_test.go @@ -2,13 +2,14 @@ package transaction import ( "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/core/validators" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" "sync" "testing" @@ -20,7 +21,7 @@ func TestDeclareCandidacyTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -29,7 +30,7 @@ func TestDeclareCandidacyTx(t *testing.T) { var publicKey types.Pubkey copy(publicKey[:], publicKeyBytes) - commission := uint(10) + commission := uint32(10) data := DeclareCandidacyData{ Address: addr, @@ -65,8 +66,7 @@ func TestDeclareCandidacyTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) - + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -91,6 +91,10 @@ func TestDeclareCandidacyTx(t *testing.T) { t.Fatalf("Reward address is not correct") } + if candidate.ControlAddress != addr { + t.Fatalf("Control address is not correct") + } + if candidate.GetTotalBipStake() != nil && candidate.GetTotalBipStake().Cmp(types.Big0) != 0 { t.Fatalf("Total stake is not correct") } @@ -102,6 +106,8 @@ func TestDeclareCandidacyTx(t *testing.T) { if candidate.Status != candidates.CandidateStatusOffline { t.Fatalf("Incorrect candidate status") } + + checkState(t, cState) } func TestDeclareCandidacyTxOverflow(t *testing.T) { @@ -110,16 +116,16 @@ func TestDeclareCandidacyTxOverflow(t *testing.T) { for i := 0; i < maxCandidatesCount; i++ { pubkey := types.Pubkey{byte(i)} - cState.Candidates.Create(types.Address{}, types.Address{}, pubkey, 10) - cState.Candidates.Delegate(types.Address{}, pubkey, types.GetBaseCoin(), helpers.BipToPip(big.NewInt(10)), helpers.BipToPip(big.NewInt(10))) + cState.Candidates.Create(types.Address{}, types.Address{}, types.Address{}, pubkey, 10) + cState.Candidates.Delegate(types.Address{}, pubkey, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10)), helpers.BipToPip(big.NewInt(10))) } - cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock3) + cState.Candidates.RecalculateStakes(109000) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -131,7 +137,7 @@ func TestDeclareCandidacyTxOverflow(t *testing.T) { data := DeclareCandidacyData{ Address: addr, PubKey: publicKey, - Commission: uint(10), + Commission: uint32(10), Coin: coin, Stake: helpers.BipToPip(big.NewInt(10)), } @@ -162,9 +168,481 @@ func TestDeclareCandidacyTxOverflow(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.TooLowStake { t.Fatalf("Response code is not %d. Got %d", code.TooLowStake, response.Code) } + + checkState(t, cState) +} + +func TestDeclareCandidacyTxWithBlockPybKey(t *testing.T) { + cState := getState() + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + cState.Candidates.Create(types.Address{}, types.Address{}, types.Address{}, publicKey, 10) + pkeyNew, _ := crypto.GenerateKey() + publicKeyNewBytes := crypto.FromECDSAPub(&pkeyNew.PublicKey)[:32] + var publicKeyNew types.Pubkey + copy(publicKeyNew[:], publicKeyNewBytes) + + cState.Candidates.ChangePubKey(publicKey, publicKeyNew) + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + commission := uint32(10) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: commission, + Coin: coin, + Stake: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + + if response.Code == 0 { + t.Fatal("Response code is not 1. Want error") + } + + if cState.Candidates.GetCandidate(publicKey) != nil { + t.Fatalf("Candidate found with old pub key") + } + + candidate := cState.Candidates.GetCandidate(publicKeyNew) + + if candidate == nil { + t.Fatalf("Candidate not found") + } + + if candidate.OwnerAddress == addr { + t.Fatalf("OwnerAddress has changed") + } + + if candidate.RewardAddress == addr { + t.Fatalf("RewardAddress has changed") + } + + if candidate.ControlAddress == addr { + t.Fatalf("ControlAddress has changed") + } + + checkState(t, cState) +} + +func TestDeclareCandidacyToNonExistCoin(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + commission := uint32(10) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: commission, + Coin: 5, + Stake: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestDeclareCandidacyToExistCandidate(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + cState.Candidates.Create(addr, addr, addr, publicKey, uint32(10)) + + commission := uint32(10) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: commission, + Coin: coin, + Stake: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateExists { + t.Fatalf("Response code is not %d. Error %s", code.CandidateExists, response.Log) + } + + checkState(t, cState) +} + +func TestDeclareCandidacyToDecodeError(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + commission := uint32(10) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: commission, + Coin: 5, + Stake: nil, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + response := data.Run(&tx, state.NewCheckState(cState), nil, 0) + if response.Code != code.DecodeError { + t.Fatalf("Response code is not %d. Error %s", code.DecodeError, response.Log) + } + + checkState(t, cState) +} + +func TestDeclareCandidacyToWrongCommission(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: maxCommission + 1, + Coin: coin, + Stake: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCommission { + t.Fatalf("Response code is not %d. Error %s", code.WrongCommission, response.Log) + } + + checkState(t, cState) +} + +func TestDeclareCandidacyToInsufficientFunds(t *testing.T) { + cState := getState() + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + stake := big.NewInt(1e18) + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: uint32(10), + Coin: coin, + Stake: stake, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) + + cState.Accounts.AddBalance(addr, coin, stake) + cState.Commit() + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + cState.Accounts.SetBalance(addr, coin, new(big.Int).Mul(big.NewInt(1e18), big.NewInt(10))) + cState.Commit() + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestDeclareCandidacyTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + stake := big.NewInt(1e18) + + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Commit() + + pkey, _ := crypto.GenerateKey() + publicKeyBytes := crypto.FromECDSAPub(&pkey.PublicKey)[:32] + var publicKey types.Pubkey + copy(publicKey[:], publicKeyBytes) + + data := DeclareCandidacyData{ + Address: addr, + PubKey: publicKey, + Commission: uint32(10), + Coin: coin, + Stake: stake, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDeclareCandidacy, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/decoder.go b/core/transaction/decoder.go index 271be9614..0a1b8ae8b 100644 --- a/core/transaction/decoder.go +++ b/core/transaction/decoder.go @@ -26,6 +26,12 @@ func init() { TxDecoder.RegisterType(TypeMultisend, MultisendData{}) TxDecoder.RegisterType(TypeCreateMultisig, CreateMultisigData{}) TxDecoder.RegisterType(TypeEditCandidate, EditCandidateData{}) + TxDecoder.RegisterType(TypeSetHaltBlock, SetHaltBlockData{}) + TxDecoder.RegisterType(TypeRecreateCoin, RecreateCoinData{}) + TxDecoder.RegisterType(TypeEditCoinOwner, EditCoinOwnerData{}) + TxDecoder.RegisterType(TypeEditMultisig, EditMultisigData{}) + TxDecoder.RegisterType(TypePriceVote, PriceVoteData{}) + TxDecoder.RegisterType(TypeEditCandidatePublicKey, EditCandidatePublicKeyData{}) } type Decoder struct { diff --git a/core/transaction/decoder_test.go b/core/transaction/decoder_test.go new file mode 100644 index 000000000..15eb56ff2 --- /dev/null +++ b/core/transaction/decoder_test.go @@ -0,0 +1,120 @@ +package transaction + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "math/big" + "testing" +) + +func TestDecodeFromBytesToInvalidSignature(t *testing.T) { + data := SendData{Coin: 0, To: types.Address{0}, Value: big.NewInt(0)} + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + _, err = TxDecoder.DecodeFromBytes(encodedTx) + if err == nil { + t.Fatal("Expected the invalid signature error") + } +} + +func TestDecodeSigToInvalidMultiSignature(t *testing.T) { + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSend, + Data: nil, + SignatureType: SigTypeMulti, + } + + _, err := DecodeSig(&tx) + if err == nil { + t.Fatal("Expected the invalid signature error") + } +} + +func TestDecodeSigToInvalidSingleSignature(t *testing.T) { + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSend, + Data: nil, + SignatureType: SigTypeSingle, + } + + _, err := DecodeSig(&tx) + if err == nil { + t.Fatal("Expected the invalid signature error") + } +} + +func TestDecodeSigToUnknownSignatureType(t *testing.T) { + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSend, + Data: nil, + SignatureType: 0x03, + } + + _, err := DecodeSig(&tx) + if err == nil { + t.Fatal("Expected unknown signature type error") + } +} + +func TestDecodeFromBytesWithoutSigToInvalidData(t *testing.T) { + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: 0x20, + Data: nil, + SignatureType: SigTypeSingle, + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + _, err = TxDecoder.DecodeFromBytesWithoutSig(encodedTx) + if err == nil { + t.Fatal("Expected tx type is not registered error") + } + + tx.Type = TypeSend + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + _, err = TxDecoder.DecodeFromBytesWithoutSig(encodedTx) + if err == nil { + t.Fatal("Expected invalid data error") + } +} diff --git a/core/transaction/delegate.go b/core/transaction/delegate.go index 5e4c2a551..d48f186b6 100644 --- a/core/transaction/delegate.go +++ b/core/transaction/delegate.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -16,60 +15,63 @@ import ( type DelegateData struct { PubKey types.Pubkey - Coin types.CoinSymbol + Coin types.CoinID Value *big.Int } -func (data DelegateData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - PubKey string `json:"pub_key"` - Coin string `json:"coin"` - Value string `json:"value"` - }{ - PubKey: data.PubKey.String(), - Coin: data.Coin.String(), - Value: data.Value.String(), - }) -} - -func (data DelegateData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data DelegateData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data DelegateData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.Value == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } + } + + if !context.Coins().Exists(tx.GasCoin) { + return &Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin), + Info: EncodeError(code.NewCoinNotExists("", tx.GasCoin.String())), + } } - if !context.Coins.Exists(tx.GasCoin) { + if !context.Coins().Exists(data.Coin) { return &Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin)} + Log: fmt.Sprintf("Coin %s not exists", data.Coin), + Info: EncodeError(code.NewCoinNotExists("", data.Coin.String())), + } + } + + sender, _ := tx.Sender() + value := big.NewInt(0).Set(data.Value) + if waitList := context.WaitList().Get(sender, data.PubKey, data.Coin); waitList != nil { + value.Add(value, waitList.Value) } - if data.Value.Cmp(types.Big0) < 1 { + if value.Cmp(types.Big0) < 1 { return &Response{ Code: code.StakeShouldBePositive, - Log: fmt.Sprintf("Stake should be positive")} + Log: "Stake should be positive", + Info: EncodeError(code.NewStakeShouldBePositive(value.String())), + } } - if !context.Candidates.Exists(data.PubKey) { + if !context.Candidates().Exists(data.PubKey) { return &Response{ Code: code.CandidateNotFound, - Log: fmt.Sprintf("Candidate with such public key not found"), - Info: EncodeError(map[string]string{ - "pub_key": data.PubKey.String(), - }), + Log: "Candidate with such public key not found", + Info: EncodeError(code.NewCandidateNotFound(data.PubKey.String())), } } - sender, _ := tx.Sender() - if !context.Candidates.IsDelegatorStakeSufficient(sender, data.PubKey, data.Coin, data.Value) { + if !context.Candidates().IsDelegatorStakeSufficient(sender, data.PubKey, data.Coin, value) { return &Response{ Code: code.TooLowStake, - Log: fmt.Sprintf("Stake is too low")} + Log: "Stake is too low", + Info: EncodeError(code.NewTooLowStake(sender.String(), data.PubKey.String(), value.String(), data.Coin.String(), context.Coins().GetCoin(data.Coin).GetFullSymbol())), + } } return nil @@ -84,10 +86,16 @@ func (data DelegateData) Gas() int64 { return commissions.DelegateTx } -func (data DelegateData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data DelegateData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -95,50 +103,31 @@ func (data DelegateData) Run(tx *Transaction, context *state.State, isCheck bool commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + coin := checkState.Coins().GetCoin(data.Coin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if context.Accounts.GetBalance(sender, data.Coin).Cmp(data.Value) < 0 { + if checkState.Accounts().GetBalance(sender, data.Coin).Cmp(data.Value) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Value, data.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": data.Value.String(), - "coin": fmt.Sprintf("%s", data.Coin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.Value, coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), data.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -147,29 +136,32 @@ func (data DelegateData) Run(tx *Transaction, context *state.State, isCheck bool totalTxCost.Add(totalTxCost, data.Value) totalTxCost.Add(totalTxCost, commission) - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(totalTxCost) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": totalTxCost.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Accounts.SubBalance(sender, data.Coin, data.Value) + + value := big.NewInt(0).Set(data.Value) + if waitList := deliverState.Waitlist.Get(sender, data.PubKey, data.Coin); waitList != nil { + value.Add(value, waitList.Value) + deliverState.Waitlist.Delete(sender, data.PubKey, data.Coin) + } - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Accounts.SubBalance(sender, data.Coin, data.Value) - context.Candidates.Delegate(sender, data.PubKey, data.Coin, data.Value, big.NewInt(0)) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Candidates.Delegate(sender, data.PubKey, data.Coin, value, big.NewInt(0)) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ diff --git a/core/transaction/delegate_test.go b/core/transaction/delegate_test.go index 09036d299..6e06c68bc 100644 --- a/core/transaction/delegate_test.go +++ b/core/transaction/delegate_test.go @@ -1,12 +1,14 @@ package transaction import ( + "encoding/binary" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" "math/rand" "sync" @@ -18,7 +20,7 @@ func createTestCandidate(stateDB *state.State) types.Pubkey { pubkey := types.Pubkey{} rand.Read(pubkey[:]) - stateDB.Candidates.Create(address, address, pubkey, 10) + stateDB.Candidates.Create(address, address, address, pubkey, 10) return pubkey } @@ -31,7 +33,7 @@ func TestDelegateTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -69,7 +71,7 @@ func TestDelegateTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -81,7 +83,7 @@ func TestDelegateTx(t *testing.T) { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) } - cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock3) + cState.Candidates.RecalculateStakes(109000) stake := cState.Candidates.GetStakeOfAddress(pubkey, addr, coin) @@ -92,4 +94,477 @@ func TestDelegateTx(t *testing.T) { if stake.Value.Cmp(value) != 0 { t.Fatalf("Stake value is not corrent. Expected %s, got %s", value, stake.Value) } + + checkState(t, cState) +} + +func TestDelegateTxWithWaitlist(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + waitlistAmount := helpers.BipToPip(big.NewInt(1000)) + + cState.Waitlist.AddWaitList(addr, pubkey, coin, waitlistAmount) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + cState.Candidates.RecalculateStakes(109000) + stake := cState.Candidates.GetStakeOfAddress(pubkey, addr, coin) + if stake == nil { + t.Fatalf("Stake not found") + } + + amount := new(big.Int).Add(value, waitlistAmount) + if stake.Value.Cmp(amount) != 0 { + t.Fatalf("Stake value is not corrent. Expected %s, got %s", amount, stake.Value) + } + + wl := cState.Waitlist.Get(addr, pubkey, coin) + if wl != nil { + t.Fatalf("Waitlist is not deleted") + } + + checkState(t, cState) +} + +func TestDelegateTxToNonExistCoin(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + data := DelegateData{ + PubKey: pubkey, + Coin: 5, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateTxToPositiveStake(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := big.NewInt(0) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.StakeShouldBePositive { + t.Fatalf("Response code is not %d. Error %s", code.StakeShouldBePositive, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateTxToNonExistCandidate(t *testing.T) { + cState := getState() + + pubkey := types.Pubkey{1} + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(10000)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateTxToLowStake(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(1000)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + for i := uint64(0); i < 1000; i++ { + var addr3 types.Address + binary.BigEndian.PutUint64(addr3[:], i) + cState.Candidates.Delegate(addr3, pubkey, coin, helpers.BipToPip(big.NewInt(12000)), helpers.BipToPip(big.NewInt(12000))) + } + + cState.Candidates.RecalculateStakes(0) + cState.Commit() + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.TooLowStake { + t.Fatalf("Response code is not %d. Error %s", code.TooLowStake, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateTxToInsufficientFunds(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := big.NewInt(1) + customCoin := createTestCoin(cState) + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) + + cState.Accounts.AddBalance(addr, coin, big.NewInt(2e17)) + + data.Coin = customCoin + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + data.Coin = coin + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + value := big.NewInt(1) + + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + + data := DelegateData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestDelegateData_addFromWaitlist(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Waitlist.AddWaitList(addr, pubkey, 0, big.NewInt(100)) + cState.Checker.AddCoin(0, big.NewInt(0).Neg(big.NewInt(100))) + cState.Accounts.AddBalance(addr, 0, helpers.BipToPip(big.NewInt(1000000))) + cState.Checker.AddCoin(0, helpers.BipToPip(big.NewInt(0).Neg(big.NewInt(1000000)))) + + value := big.NewInt(10000000000) + data := DelegateData{ + PubKey: pubkey, + Coin: 0, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: 0, + Type: TypeDelegate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + rewards := big.NewInt(0) + response := RunTx(cState, encodedTx, rewards, 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not %d. Error %s", code.OK, response.Log) + } + cState.Checker.AddCoin(0, rewards) + + err = cState.Check() + if err != nil { + t.Fatal(err) + } + + checkState(t, cState) } diff --git a/core/transaction/edit_candidate.go b/core/transaction/edit_candidate.go index 84bbad50f..3f7616b87 100644 --- a/core/transaction/edit_candidate.go +++ b/core/transaction/edit_candidate.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -18,32 +17,17 @@ type CandidateTx interface { } type EditCandidateData struct { - PubKey types.Pubkey - RewardAddress types.Address - OwnerAddress types.Address -} - -func (data EditCandidateData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - PubKey string `json:"pub_key"` - RewardAddress string `json:"reward_address"` - OwnerAddress string `json:"owner_address"` - }{ - PubKey: data.PubKey.String(), - RewardAddress: data.RewardAddress.String(), - OwnerAddress: data.OwnerAddress.String(), - }) + PubKey types.Pubkey + RewardAddress types.Address + OwnerAddress types.Address + ControlAddress types.Address } func (data EditCandidateData) GetPubKey() types.Pubkey { return data.PubKey } -func (data EditCandidateData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data EditCandidateData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data EditCandidateData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { return checkCandidateOwnership(data, tx, context) } @@ -56,10 +40,16 @@ func (data EditCandidateData) Gas() int64 { return commissions.EditCandidate } -func (data EditCandidateData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data EditCandidateData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -67,50 +57,34 @@ func (data EditCandidateData) Run(tx *Transaction, context *state.State, isCheck commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Candidates.Edit(data.PubKey, data.RewardAddress, data.OwnerAddress) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Candidates.Edit(data.PubKey, data.RewardAddress, data.OwnerAddress, data.ControlAddress) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ @@ -126,23 +100,23 @@ func (data EditCandidateData) Run(tx *Transaction, context *state.State, isCheck } } -func checkCandidateOwnership(data CandidateTx, tx *Transaction, context *state.State) *Response { - if !context.Candidates.Exists(data.GetPubKey()) { +func checkCandidateOwnership(data CandidateTx, tx *Transaction, context *state.CheckState) *Response { + if !context.Candidates().Exists(data.GetPubKey()) { return &Response{ Code: code.CandidateNotFound, Log: fmt.Sprintf("Candidate with such public key (%s) not found", data.GetPubKey().String()), - Info: EncodeError(map[string]string{ - "public_key": data.GetPubKey().String(), - }), + Info: EncodeError(code.NewCandidateNotFound(data.GetPubKey().String())), } } - owner := context.Candidates.GetCandidateOwner(data.GetPubKey()) + owner := context.Candidates().GetCandidateOwner(data.GetPubKey()) sender, _ := tx.Sender() if owner != sender { return &Response{ Code: code.IsNotOwnerOfCandidate, - Log: fmt.Sprintf("Sender is not an owner of a candidate")} + Log: "Sender is not an owner of a candidate", + Info: EncodeError(code.NewIsNotOwnerOfCandidate(sender.String(), data.GetPubKey().String(), owner.String(), "")), + } } return nil diff --git a/core/transaction/edit_candidate_public_key.go b/core/transaction/edit_candidate_public_key.go new file mode 100644 index 000000000..b19440c51 --- /dev/null +++ b/core/transaction/edit_candidate_public_key.go @@ -0,0 +1,120 @@ +package transaction + +import ( + "encoding/hex" + "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/kv" + "math/big" +) + +type EditCandidatePublicKeyData struct { + PubKey types.Pubkey + NewPubKey types.Pubkey +} + +func (data EditCandidatePublicKeyData) GetPubKey() types.Pubkey { + return data.PubKey +} + +func (data EditCandidatePublicKeyData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + return checkCandidateOwnership(data, tx, context) +} + +func (data EditCandidatePublicKeyData) String() string { + return fmt.Sprintf("EDIT CANDIDATE PUB KEY old: %x, new: %x", + data.PubKey, data.NewPubKey) +} + +func (data EditCandidatePublicKeyData) Gas() int64 { + return commissions.EditCandidatePublicKey +} + +func (data EditCandidatePublicKeyData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + if data.PubKey == data.NewPubKey { + return Response{ + Code: code.NewPublicKeyIsBad, + Log: fmt.Sprintf("Current public key (%s) equals new public key (%s)", data.PubKey.String(), data.NewPubKey.String()), + Info: EncodeError(code.NewNewPublicKeyIsBad(data.PubKey.String(), data.NewPubKey.String())), + } + } + + if checkState.Candidates().Exists(data.NewPubKey) { + return Response{ + Code: code.CandidateExists, + Log: fmt.Sprintf("Candidate with such public key (%s) already exists", data.NewPubKey.String()), + Info: EncodeError(code.NewCandidateExists(data.NewPubKey.String())), + } + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if checkState.Candidates().IsBlockedPubKey(data.NewPubKey) { + return Response{ + Code: code.PublicKeyInBlockList, + Log: fmt.Sprintf("Public key (%s) exists in block list", data.NewPubKey.String()), + Info: EncodeError(code.NewPublicKeyInBlockList(data.NewPubKey.String())), + } + } + + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Candidates.ChangePubKey(data.PubKey, data.NewPubKey) + + deliverState.Accounts.SetNonce(sender, tx.Nonce) + } + + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeEditCandidatePublicKey)}))}, + kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, + } + + return Response{ + Code: code.OK, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + Tags: tags, + } +} diff --git a/core/transaction/edit_candidate_public_key_test.go b/core/transaction/edit_candidate_public_key_test.go new file mode 100644 index 000000000..f87b1d7dd --- /dev/null +++ b/core/transaction/edit_candidate_public_key_test.go @@ -0,0 +1,463 @@ +package transaction + +import ( + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" + "math/big" + "math/rand" + "sync" + "testing" +) + +func TestEditCandidateNewPublicKeyTx(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + newpubkey := [32]byte{} + rand.Read(newpubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Commit() + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: newpubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + targetBalance, _ := big.NewInt(0).SetString("900000000000000000000000", 10) + balance := cState.Accounts.GetBalance(addr, coin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) + } + + if cState.Candidates.GetCandidate(pubkey) != nil { + t.Fatalf("Candidate found with old pub key") + } + + candidate := cState.Candidates.GetCandidate(newpubkey) + if candidate == nil { + t.Fatalf("Candidate not found") + } + + if !candidate.PubKey.Equals(newpubkey) { + t.Fatalf("Public key has not changed") + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyTxToNewPublicKey(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.NewPublicKeyIsBad { + t.Fatalf("Response code is not %d. Error %s", code.NewPublicKeyIsBad, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyTxToNewPublicKeyInBlockList(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + pubkey2 := types.Pubkey{1} + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Candidates.AddToBlockPubKey(pubkey2) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: pubkey2, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.PublicKeyInBlockList { + t.Fatalf("Response code is not %d. Error %s", code.PublicKeyInBlockList, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyTxToInsufficientFunds(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: types.Pubkey{5}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: types.Pubkey{5}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyToNotExistCandidate(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: types.Pubkey{5}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyTxToCandidateOwnership(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + addr2 := types.Address{0} + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr2, addr2, addr2, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: types.Pubkey{5}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCandidate { + t.Fatalf("Response code is not %d. Error %s", code.IsNotOwnerOfCandidate, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidatePublicKeyData_Exists(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + newpubkey := [32]byte{} + rand.Read(newpubkey[:]) + + cState.Candidates.Create(addr, addr, addr, newpubkey, 10) + cState.Validators.Create(newpubkey, helpers.BipToPip(big.NewInt(1))) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + candidate1 := cState.Candidates.GetCandidate(newpubkey) + if candidate1 == nil { + t.Fatalf("Candidate not found") + } + candidate2 := cState.Candidates.GetCandidate(pubkey) + if candidate2 == nil { + t.Fatalf("Candidate not found") + } + + data := EditCandidatePublicKeyData{ + PubKey: pubkey, + NewPubKey: newpubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidatePublicKey, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateExists { + t.Fatalf("Response code is %d. Error %s", response.Code, response.Log) + } + + if candidate1.PubKey == candidate2.PubKey { + t.Fatalf("Candidates pulic keys are equal") + } + + checkState(t, cState) +} diff --git a/core/transaction/edit_candidate_test.go b/core/transaction/edit_candidate_test.go index 0f5523395..1d6d59f50 100644 --- a/core/transaction/edit_candidate_test.go +++ b/core/transaction/edit_candidate_test.go @@ -1,14 +1,16 @@ package transaction import ( - "github.com/MinterTeam/minter-go-node/core/types" - "github.com/MinterTeam/minter-go-node/crypto" - "github.com/MinterTeam/minter-go-node/helpers" - "github.com/MinterTeam/minter-go-node/rlp" "math/big" "math/rand" "sync" "testing" + + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" ) func TestEditCandidateTx(t *testing.T) { @@ -16,22 +18,24 @@ func TestEditCandidateTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) pubkey := [32]byte{} rand.Read(pubkey[:]) - cState.Candidates.Create(addr, addr, pubkey, 10) + cState.Candidates.Create(addr, addr, addr, pubkey, 10) cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) newRewardAddress := types.Address{1} newOwnerAddress := types.Address{2} + newControlAddress := types.Address{3} data := EditCandidateData{ - PubKey: pubkey, - RewardAddress: newRewardAddress, - OwnerAddress: newOwnerAddress, + PubKey: pubkey, + RewardAddress: newRewardAddress, + OwnerAddress: newOwnerAddress, + ControlAddress: newControlAddress, } encodedData, err := rlp.EncodeToBytes(data) @@ -60,7 +64,7 @@ func TestEditCandidateTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -85,4 +89,237 @@ func TestEditCandidateTx(t *testing.T) { if candidate.RewardAddress != newRewardAddress { t.Fatalf("RewardAddress has not changed") } + + if candidate.ControlAddress != newControlAddress { + t.Fatalf("ControlAddress has not changed") + } + + checkState(t, cState) +} + +func TestEditCandidateTxToNonExistCandidate(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + newRewardAddress := types.Address{1} + newOwnerAddress := types.Address{2} + newControlAddress := types.Address{3} + + data := EditCandidateData{ + PubKey: pubkey, + RewardAddress: newRewardAddress, + OwnerAddress: newOwnerAddress, + ControlAddress: newControlAddress, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidateTxToCandidateOwnership(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + addr2 := types.Address{0} + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr2, addr2, addr2, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + newRewardAddress := types.Address{1} + newOwnerAddress := types.Address{2} + newControlAddress := types.Address{3} + + data := EditCandidateData{ + PubKey: pubkey, + RewardAddress: newRewardAddress, + OwnerAddress: newOwnerAddress, + ControlAddress: newControlAddress, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCandidate { + t.Fatalf("Response code is not %d. Error %s", code.IsNotOwnerOfCandidate, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidateTxToInsufficientFunds(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + newRewardAddress := types.Address{1} + newOwnerAddress := types.Address{2} + newControlAddress := types.Address{3} + + data := EditCandidateData{ + PubKey: pubkey, + RewardAddress: newRewardAddress, + OwnerAddress: newOwnerAddress, + ControlAddress: newControlAddress, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestEditCandidateTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + newRewardAddress := types.Address{1} + newOwnerAddress := types.Address{2} + newControlAddress := types.Address{3} + + data := EditCandidateData{ + PubKey: pubkey, + RewardAddress: newRewardAddress, + OwnerAddress: newOwnerAddress, + ControlAddress: newControlAddress, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditCandidate, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/edit_coin_owner.go b/core/transaction/edit_coin_owner.go new file mode 100644 index 000000000..e97a41475 --- /dev/null +++ b/core/transaction/edit_coin_owner.go @@ -0,0 +1,111 @@ +package transaction + +import ( + "encoding/hex" + "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/kv" + "math/big" +) + +type EditCoinOwnerData struct { + Symbol types.CoinSymbol + NewOwner types.Address +} + +func (data EditCoinOwnerData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + sender, _ := tx.Sender() + + info := context.Coins().GetSymbolInfo(data.Symbol) + if info == nil { + return &Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", data.Symbol), + Info: EncodeError(code.NewCoinNotExists(data.Symbol.String(), "")), + } + } + + if info.OwnerAddress() == nil || *info.OwnerAddress() != sender { + owner := info.OwnerAddress().String() + return &Response{ + Code: code.IsNotOwnerOfCoin, + Log: "Sender is not owner of coin", + Info: EncodeError(code.NewIsNotOwnerOfCoin(data.Symbol.String(), &owner)), + } + } + + return nil +} + +func (data EditCoinOwnerData) String() string { + return fmt.Sprintf("EDIT OWNER COIN symbol:%s new owner:%s", data.Symbol.String(), data.NewOwner.String()) +} + +func (data EditCoinOwnerData) Gas() int64 { + return commissions.EditOwner +} + +func (data EditCoinOwnerData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoinID() { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Coins.ChangeOwner(data.Symbol, data.NewOwner) + deliverState.Accounts.SetNonce(sender, tx.Nonce) + } + + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeEditCoinOwner)}))}, + kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, + kv.Pair{Key: []byte("tx.coin_symbol"), Value: []byte(data.Symbol.String())}, + } + + return Response{ + Code: code.OK, + Tags: tags, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + } +} diff --git a/core/transaction/edit_coin_owner_test.go b/core/transaction/edit_coin_owner_test.go new file mode 100644 index 000000000..33a7f613d --- /dev/null +++ b/core/transaction/edit_coin_owner_test.go @@ -0,0 +1,254 @@ +package transaction + +import ( + "crypto/ecdsa" + + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" + + "math/big" + "sync" + "testing" + + db "github.com/tendermint/tm-db" +) + +func TestEditOwnerTx(t *testing.T) { + cState, err := state.NewState(0, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + newOwnerPrivateKey, _ := crypto.GenerateKey() + newOwner := crypto.PubkeyToAddress(newOwnerPrivateKey.PublicKey) + + createTestCoinWithOwner(cState, addr) + createDefaultValidator(cState) + + gasCoin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, gasCoin, helpers.BipToPip(big.NewInt(10000))) + + data := EditCoinOwnerData{ + Symbol: getTestCoinSymbol(), + NewOwner: newOwner, + } + + tx, err := makeTestEditOwnerTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + err = cState.Coins.Commit() + if err != nil { + t.Fatalf("Failed to commit coins: %s", err) + } + + targetBalance, _ := big.NewInt(0).SetString("0", 10) + balance := cState.Accounts.GetBalance(addr, gasCoin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %d, got %s", gasCoin, targetBalance, balance) + } + + symbol := cState.Coins.GetSymbolInfo(getTestCoinSymbol()) + if symbol == nil { + t.Fatal("Symbol info not found") + } + + if *symbol.OwnerAddress() != newOwner { + t.Fatalf("Target owner address is not correct. Excpected %s, got %s", newOwner.String(), symbol.OwnerAddress().String()) + } + + checkState(t, cState) +} + +func TestEditOwnerTxWithWrongOwner(t *testing.T) { + cState, err := state.NewState(0, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + privateKey, _ := crypto.GenerateKey() + + newOwnerPrivateKey, _ := crypto.GenerateKey() + newOwner := crypto.PubkeyToAddress(newOwnerPrivateKey.PublicKey) + + createTestCoinWithOwner(cState, newOwner) + createDefaultValidator(cState) + + data := EditCoinOwnerData{ + Symbol: getTestCoinSymbol(), + NewOwner: newOwner, + } + + tx, err := makeTestEditOwnerTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCoin { + t.Fatalf("Response code is not 206. Error %s", response.Log) + } + + checkState(t, cState) +} + +func TestEditOwnerTxWithWrongSymbol(t *testing.T) { + cState, err := state.NewState(0, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + newOwnerPrivateKey, _ := crypto.GenerateKey() + newOwner := crypto.PubkeyToAddress(newOwnerPrivateKey.PublicKey) + + createTestCoinWithOwner(cState, addr) + createDefaultValidator(cState) + + data := EditCoinOwnerData{ + Symbol: types.StrToCoinSymbol("UNKNOWN"), + NewOwner: newOwner, + } + + tx, err := makeTestEditOwnerTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not 102. Error %s", response.Log) + } + + checkState(t, cState) +} + +func TestEditCOwnerTxWithInsufficientFunds(t *testing.T) { + cState, err := state.NewState(0, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + newOwnerPrivateKey, _ := crypto.GenerateKey() + newOwner := crypto.PubkeyToAddress(newOwnerPrivateKey.PublicKey) + + createTestCoinWithOwner(cState, addr) + createDefaultValidator(cState) + + data := EditCoinOwnerData{ + Symbol: getTestCoinSymbol(), + NewOwner: newOwner, + } + + tx, err := makeTestEditOwnerTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestEditCoinOwnerTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + newOwnerPrivateKey, _ := crypto.GenerateKey() + newOwner := crypto.PubkeyToAddress(newOwnerPrivateKey.PublicKey) + + customCoin := createTestCoinWithOwner(cState, addr) + cState.Coins.SubReserve(customCoin, helpers.BipToPip(big.NewInt(90000))) + + data := EditCoinOwnerData{ + Symbol: types.StrToCoinSymbol("TEST"), + NewOwner: newOwner, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: customCoin, + Type: TypeEditCoinOwner, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func makeTestEditOwnerTx(data EditCoinOwnerData, privateKey *ecdsa.PrivateKey) ([]byte, error) { + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + return nil, err + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeEditCoinOwner, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + return nil, err + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + return nil, err + } + + return encodedTx, nil +} + +func createDefaultValidator(cState *state.State) { + cState.Validators.Create(types.Pubkey{0}, big.NewInt(0)) + cState.Candidates.Create(types.Address{0}, types.Address{0}, types.Address{0}, types.Pubkey{0}, 0) +} diff --git a/core/transaction/edit_multisig.go b/core/transaction/edit_multisig.go new file mode 100644 index 000000000..a07229729 --- /dev/null +++ b/core/transaction/edit_multisig.go @@ -0,0 +1,158 @@ +package transaction + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" + + "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/kv" +) + +type EditMultisigData struct { + Threshold uint32 + Weights []uint32 + Addresses []types.Address +} + +func (data EditMultisigData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + sender, _ := tx.Sender() + + if !context.Accounts().GetAccount(sender).IsMultisig() { + return &Response{ + Code: code.MultisigNotExists, + Log: "Multisig does not exists", + Info: EncodeError(code.NewMultisigNotExists(sender.String())), + } + } + + lenWeights := len(data.Weights) + if lenWeights > 32 { + return &Response{ + Code: code.TooLargeOwnersList, + Log: "Owners list is limited to 32 items", + Info: EncodeError(code.NewTooLargeOwnersList(strconv.Itoa(lenWeights), "32")), + } + } + + lenAddresses := len(data.Addresses) + if lenAddresses != lenWeights { + return &Response{ + Code: code.DifferentCountAddressesAndWeights, + Log: "Different count addresses and weights", + Info: EncodeError(code.NewDifferentCountAddressesAndWeights(fmt.Sprintf("%d", lenAddresses), fmt.Sprintf("%d", lenWeights))), + } + } + + for i, weight := range data.Weights { + if weight > 1023 { + return &Response{ + Code: code.IncorrectWeights, + Log: "Incorrect multisig weights", + Info: EncodeError(code.NewIncorrectWeights(data.Addresses[i].String(), strconv.Itoa(int(weight)), "1024")), + } + } + } + + usedAddresses := map[types.Address]bool{} + for _, address := range data.Addresses { + if usedAddresses[address] { + return &Response{ + Code: code.DuplicatedAddresses, + Log: "Duplicated multisig addresses", + Info: EncodeError(code.NewDuplicatedAddresses(address.String())), + } + } + + usedAddresses[address] = true + } + + var totalWeight uint32 + for _, weight := range data.Weights { + totalWeight += weight + } + if data.Threshold > totalWeight { + return &Response{ + Code: code.IncorrectTotalWeights, + Log: "Incorrect multisig weights", + Info: EncodeError(code.NewIncorrectTotalWeights(fmt.Sprintf("%d", totalWeight), fmt.Sprintf("%d", data.Threshold))), + } + } + + return nil +} + +func (data EditMultisigData) String() string { + return "EDIT MULTISIG OWNERS" +} + +func (data EditMultisigData) Gas() int64 { + return commissions.EditMultisigData +} + +func (data EditMultisigData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + + deliverState.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Accounts.SetNonce(sender, tx.Nonce) + + deliverState.Accounts.EditMultisig(data.Threshold, data.Weights, data.Addresses, sender) + } + + address := []byte(hex.EncodeToString(sender[:])) + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeEditMultisig)}))}, + kv.Pair{Key: []byte("tx.from"), Value: address}, + } + + return Response{ + Code: code.OK, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + Tags: tags, + } +} diff --git a/core/transaction/edit_multisig_test.go b/core/transaction/edit_multisig_test.go new file mode 100644 index 000000000..d52c6c688 --- /dev/null +++ b/core/transaction/edit_multisig_test.go @@ -0,0 +1,474 @@ +package transaction + +import ( + "math/big" + "math/rand" + "reflect" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" +) + +func TestEditMultisigTx(t *testing.T) { + cState := getState() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + privateKey4, _ := crypto.GenerateKey() + addr4 := crypto.PubkeyToAddress(privateKey4.PublicKey) + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + coin := types.GetBaseCoinID() + initialBalance := big.NewInt(1) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(initialBalance)) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{2, 1, 2}, + Addresses: []types.Address{addr1, addr2, addr4}, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + account := cState.Accounts.GetAccount(addr) + + if !account.IsMultisig() { + t.Fatalf("Multisig %s is not created", addr.String()) + } + + msigData := account.Multisig() + + if !reflect.DeepEqual(msigData.Addresses, data.Addresses) { + t.Fatalf("Addresses are not correct") + } + + if !reflect.DeepEqual(msigData.Weights, data.Weights) { + t.Fatalf("Weights are not correct") + } + + if msigData.Threshold != 3 { + t.Fatalf("Threshold is not correct") + } + + checkState(t, cState) +} + +func TestEditMultisigTxToNonExistAddress(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{2, 1, 2}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + checkState := state.NewCheckState(cState) + response := data.BasicCheck(&tx, checkState) + if response.Code != code.MultisigNotExists { + t.Fatalf("Response code is not %d. Error %s", code.MultisigNotExists, response.Log) + } +} + +func TestEditMultisigTxToTooLargeOwnersList(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + weights := make([]uint32, 33) + for i := uint32(0); i <= 32; i++ { + weights[i] = i + } + + data := EditMultisigData{ + Threshold: 3, + Weights: weights, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.TooLargeOwnersList { + t.Fatalf("Response code is not %d. Error %s", code.TooLargeOwnersList, response.Log) + } + + checkState(t, cState) +} + +func TestEditMultisigTxIncorrectWeights(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3, 4}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.DifferentCountAddressesAndWeights { + t.Fatalf("Response code is not %d. Error %s", code.DifferentCountAddressesAndWeights, response.Log) + } + + checkState(t, cState) + + data.Weights = []uint32{1, 2, 1024} + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IncorrectWeights { + t.Fatalf("Response code is not %d. Error %s", code.IncorrectWeights, response.Log) + } + + checkState(t, cState) + + data.Weights = []uint32{1, 2, 3} + data.Threshold = 7 + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IncorrectTotalWeights { + t.Fatalf("Response code is not %d. Error %s", code.IncorrectTotalWeights, response.Log) + } + + checkState(t, cState) +} + +func TestEditMultisigTxToAddressDuplication(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr1, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.DuplicatedAddresses { + t.Fatalf("Response code is not %d. Error %s", code.DuplicatedAddresses, response.Log) + } + + checkState(t, cState) +} + +func TestEditMultisigTxToInsufficientFunds(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := types.GetBaseCoinID() + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestEditMultisigTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + addr := types.Address{0} + privateKey1, _ := crypto.GenerateKey() + addr1 := crypto.PubkeyToAddress(privateKey1.PublicKey) + privateKey2, _ := crypto.GenerateKey() + addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) + privateKey3, _ := crypto.GenerateKey() + addr3 := crypto.PubkeyToAddress(privateKey3.PublicKey) + + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + + cState.Accounts.CreateMultisig([]uint32{1, 2, 3}, []types.Address{addr1, addr2, addr3}, 3, addr) + + data := EditMultisigData{ + Threshold: 3, + Weights: []uint32{1, 2, 3}, + Addresses: []types.Address{addr1, addr2, addr3}, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeEditMultisig, + Data: encodedData, + SignatureType: SigTypeMulti, + } + + tx.SetMultisigAddress(addr) + + if err := tx.Sign(privateKey3); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} diff --git a/core/transaction/encoder/encoder.go b/core/transaction/encoder/encoder.go new file mode 100644 index 000000000..fc6c921bf --- /dev/null +++ b/core/transaction/encoder/encoder.go @@ -0,0 +1,110 @@ +package encoder + +import ( + "encoding/json" + "fmt" + + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/transaction" + rpctypes "github.com/MinterTeam/minter-go-node/rpc/lib/types" + "github.com/tendermint/tendermint/libs/bytes" + coretypes "github.com/tendermint/tendermint/rpc/core/types" +) + +type TxEncoderJSON struct { + context *state.CheckState +} + +type TransactionResponse struct { + Hash string `json:"hash"` + RawTx string `json:"raw_tx"` + Height int64 `json:"height"` + Index uint32 `json:"index"` + From string `json:"from"` + Nonce uint64 `json:"nonce"` + Gas int64 `json:"gas"` + GasPrice uint32 `json:"gas_price"` + GasCoin CoinResource `json:"gas_coin"` + Type uint8 `json:"type"` + Data json.RawMessage `json:"data"` + Payload []byte `json:"payload"` + Tags map[string]string `json:"tags"` + Code uint32 `json:"code,omitempty"` + Log string `json:"log,omitempty"` +} + +var resourcesConfig = map[transaction.TxType]TxDataResource{ + transaction.TypeSend: new(SendDataResource), + transaction.TypeSellCoin: new(SellCoinDataResource), + transaction.TypeSellAllCoin: new(SellAllCoinDataResource), + transaction.TypeBuyCoin: new(BuyCoinDataResource), + transaction.TypeCreateCoin: new(CreateCoinDataResource), + transaction.TypeDeclareCandidacy: new(DeclareCandidacyDataResource), + transaction.TypeDelegate: new(DelegateDataResource), + transaction.TypeUnbond: new(UnbondDataResource), + transaction.TypeRedeemCheck: new(RedeemCheckDataResource), + transaction.TypeSetCandidateOnline: new(SetCandidateOnDataResource), + transaction.TypeSetCandidateOffline: new(SetCandidateOffDataResource), + transaction.TypeCreateMultisig: new(CreateMultisigDataResource), + transaction.TypeMultisend: new(MultiSendDataResource), + transaction.TypeEditCandidate: new(EditCandidateDataResource), + transaction.TypeSetHaltBlock: new(SetHaltBlockDataResource), + transaction.TypeRecreateCoin: new(RecreateCoinDataResource), + transaction.TypeEditCoinOwner: new(EditCoinOwnerDataResource), + transaction.TypeEditMultisig: new(EditMultisigResource), + transaction.TypePriceVote: new(PriceVoteResource), + transaction.TypeEditCandidatePublicKey: new(EditCandidatePublicKeyResource), +} + +func NewTxEncoderJSON(context *state.CheckState) *TxEncoderJSON { + return &TxEncoderJSON{context} +} + +func (encoder *TxEncoderJSON) Encode(transaction *transaction.Transaction, tmTx *coretypes.ResultTx) (json.RawMessage, error) { + sender, _ := transaction.Sender() + + // prepare transaction data resource + data, err := encoder.EncodeData(transaction) + if err != nil { + return nil, err + } + + // prepare transaction tags + tags := make(map[string]string) + for _, tag := range tmTx.TxResult.Events[0].Attributes { + tags[string(tag.Key)] = string(tag.Value) + } + + gasCoin := encoder.context.Coins().GetCoin(transaction.GasCoin) + txGasCoin := CoinResource{gasCoin.ID().Uint32(), gasCoin.GetFullSymbol()} + + tx := TransactionResponse{ + Hash: bytes.HexBytes(tmTx.Tx.Hash()).String(), + RawTx: fmt.Sprintf("%x", []byte(tmTx.Tx)), + Height: tmTx.Height, + Index: tmTx.Index, + From: sender.String(), + Nonce: transaction.Nonce, + Gas: transaction.Gas(), + GasPrice: transaction.GasPrice, + GasCoin: txGasCoin, + Type: uint8(transaction.Type), + Data: data, + Payload: transaction.Payload, + Tags: tags, + Code: tmTx.TxResult.Code, + Log: tmTx.TxResult.Log, + } + + return json.Marshal(tx) +} + +func (encoder *TxEncoderJSON) EncodeData(decodedTx *transaction.Transaction) ([]byte, error) { + if resource, exists := resourcesConfig[decodedTx.Type]; exists { + return json.Marshal( + resource.Transform(decodedTx.GetDecodedData(), encoder.context), + ) + } + + return nil, rpctypes.RPCError{Code: 500, Message: "unknown tx type"} +} diff --git a/core/transaction/encoder/resources.go b/core/transaction/encoder/resources.go new file mode 100644 index 000000000..c1302ab54 --- /dev/null +++ b/core/transaction/encoder/resources.go @@ -0,0 +1,401 @@ +package encoder + +import ( + "encoding/base64" + "strconv" + + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" +) + +// TxDataResource is an interface for preparing JSON representation of TxData +type TxDataResource interface { + Transform(txData interface{}, context *state.CheckState) TxDataResource +} + +// CoinResource is a JSON representation of a coin +type CoinResource struct { + ID uint32 `json:"id"` + Symbol string `json:"symbol"` +} + +// SendDataResource is JSON representation of TxType 0x01 +type SendDataResource struct { + Coin CoinResource `json:"coin"` + To string `json:"to"` + Value string `json:"value"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SendDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SendData) + coin := context.Coins().GetCoin(data.Coin) + + return SendDataResource{ + To: data.To.String(), + Value: data.Value.String(), + Coin: CoinResource{coin.ID().Uint32(), coin.GetFullSymbol()}, + } +} + +// SellCoinDataResource is JSON representation of TxType 0x02 +type SellCoinDataResource struct { + CoinToSell CoinResource `json:"coin_to_sell"` + ValueToSell string `json:"value_to_sell"` + CoinToBuy CoinResource `json:"coin_to_buy"` + MinimumValueToBuy string `json:"minimum_value_to_buy"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SellCoinDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SellCoinData) + buyCoin := context.Coins().GetCoin(data.CoinToBuy) + sellCoin := context.Coins().GetCoin(data.CoinToSell) + + return SellCoinDataResource{ + ValueToSell: data.ValueToSell.String(), + MinimumValueToBuy: data.MinimumValueToBuy.String(), + CoinToBuy: CoinResource{buyCoin.ID().Uint32(), buyCoin.GetFullSymbol()}, + CoinToSell: CoinResource{sellCoin.ID().Uint32(), sellCoin.GetFullSymbol()}, + } +} + +// SellAllCoinDataResource is JSON representation of TxType 0x03 +type SellAllCoinDataResource struct { + CoinToSell CoinResource `json:"coin_to_sell"` + CoinToBuy CoinResource `json:"coin_to_buy"` + MinimumValueToBuy string `json:"minimum_value_to_buy"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SellAllCoinDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SellAllCoinData) + buyCoin := context.Coins().GetCoin(data.CoinToBuy) + sellCoin := context.Coins().GetCoin(data.CoinToSell) + + return SellAllCoinDataResource{ + MinimumValueToBuy: data.MinimumValueToBuy.String(), + CoinToBuy: CoinResource{buyCoin.ID().Uint32(), buyCoin.GetFullSymbol()}, + CoinToSell: CoinResource{sellCoin.ID().Uint32(), sellCoin.GetFullSymbol()}, + } +} + +// BuyCoinDataResource is JSON representation of TxType 0x04 +type BuyCoinDataResource struct { + CoinToBuy CoinResource `json:"coin_to_buy"` + ValueToBuy string `json:"value_to_buy"` + CoinToSell CoinResource `json:"coin_to_sell"` + MaximumValueToSell string `json:"maximum_value_to_sell"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (BuyCoinDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.BuyCoinData) + buyCoin := context.Coins().GetCoin(data.CoinToBuy) + sellCoin := context.Coins().GetCoin(data.CoinToSell) + + return BuyCoinDataResource{ + ValueToBuy: data.ValueToBuy.String(), + MaximumValueToSell: data.MaximumValueToSell.String(), + CoinToBuy: CoinResource{buyCoin.ID().Uint32(), buyCoin.GetFullSymbol()}, + CoinToSell: CoinResource{sellCoin.ID().Uint32(), sellCoin.GetFullSymbol()}, + } +} + +// CreateCoinDataResource is JSON representation of TxType 0x05 +type CreateCoinDataResource struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + InitialAmount string `json:"initial_amount"` + InitialReserve string `json:"initial_reserve"` + ConstantReserveRatio string `json:"constant_reserve_ratio"` + MaxSupply string `json:"max_supply"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (CreateCoinDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.CreateCoinData) + + return CreateCoinDataResource{ + Name: data.Name, + Symbol: data.Symbol.String(), + InitialAmount: data.InitialAmount.String(), + InitialReserve: data.InitialReserve.String(), + ConstantReserveRatio: strconv.Itoa(int(data.ConstantReserveRatio)), + MaxSupply: data.MaxSupply.String(), + } +} + +// DeclareCandidacyDataResource is JSON representation of TxType 0x06 +type DeclareCandidacyDataResource struct { + Address string `json:"address"` + PubKey string `json:"pub_key"` + Commission string `json:"commission"` + Coin CoinResource `json:"coin"` + Stake string `json:"stake"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (DeclareCandidacyDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.DeclareCandidacyData) + coin := context.Coins().GetCoin(data.Coin) + + return DeclareCandidacyDataResource{ + Address: data.Address.String(), + PubKey: data.PubKey.String(), + Commission: strconv.Itoa(int(data.Commission)), + Stake: data.Stake.String(), + Coin: CoinResource{coin.ID().Uint32(), coin.GetFullSymbol()}, + } +} + +// DelegateDataResource is JSON representation of TxType 0x07 +type DelegateDataResource struct { + PubKey string `json:"pub_key"` + Coin CoinResource `json:"coin"` + Value string `json:"value"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (DelegateDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.DelegateData) + coin := context.Coins().GetCoin(data.Coin) + + return DelegateDataResource{ + PubKey: data.PubKey.String(), + Value: data.Value.String(), + Coin: CoinResource{coin.ID().Uint32(), coin.GetFullSymbol()}, + } +} + +// UnbondDataResource is JSON representation of TxType 0x08 +type UnbondDataResource struct { + PubKey string `json:"pub_key"` + Coin CoinResource `json:"coin"` + Value string `json:"value"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (UnbondDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.UnbondData) + coin := context.Coins().GetCoin(data.Coin) + + return UnbondDataResource{ + PubKey: data.PubKey.String(), + Value: data.Value.String(), + Coin: CoinResource{coin.ID().Uint32(), coin.GetFullSymbol()}, + } +} + +// RedeemCheckDataResource is JSON representation of TxType 0x09 +type RedeemCheckDataResource struct { + RawCheck string `json:"raw_check"` + Proof string `json:"proof"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (RedeemCheckDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.RedeemCheckData) + + return RedeemCheckDataResource{ + RawCheck: base64.StdEncoding.EncodeToString(data.RawCheck), + Proof: base64.StdEncoding.EncodeToString(data.Proof[:]), + } +} + +// SetCandidateOnDataResource is JSON representation of TxType 0x0A +type SetCandidateOnDataResource struct { + PubKey string `json:"pub_key"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SetCandidateOnDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SetCandidateOnData) + return SetCandidateOnDataResource{data.PubKey.String()} +} + +// SetCandidateOffDataResource is JSON representation of TxType 0x0B +type SetCandidateOffDataResource struct { + PubKey string `json:"pub_key"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SetCandidateOffDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SetCandidateOffData) + return SetCandidateOffDataResource{data.PubKey.String()} +} + +// CreateMultisigDataResource is JSON representation of TxType 0x0C +type CreateMultisigDataResource struct { + Threshold string `json:"threshold"` + Weights []string `json:"weights"` + Addresses []types.Address `json:"addresses"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (CreateMultisigDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.CreateMultisigData) + + var weights []string + for _, weight := range data.Weights { + weights = append(weights, strconv.Itoa(int(weight))) + } + + return CreateMultisigDataResource{ + Threshold: strconv.Itoa(int(data.Threshold)), + Weights: weights, + Addresses: data.Addresses, + } +} + +// MultiSendDataResource is JSON representation of TxType 0x0D +type MultiSendDataResource struct { + List []SendDataResource `json:"list"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (resource MultiSendDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.MultisendData) + + for _, v := range data.List { + coin := context.Coins().GetCoin(v.Coin) + + resource.List = append(resource.List, SendDataResource{ + Coin: CoinResource{coin.ID().Uint32(), coin.GetFullSymbol()}, + To: v.To.String(), + Value: v.Value.String(), + }) + } + + return resource +} + +// EditCandidateDataResource is JSON representation of TxType 0x0E +type EditCandidateDataResource struct { + PubKey string `json:"pub_key"` + RewardAddress string `json:"reward_address"` + OwnerAddress string `json:"owner_address"` + ControlAddress string `json:"control_address"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (EditCandidateDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.EditCandidateData) + return EditCandidateDataResource{ + PubKey: data.PubKey.String(), + RewardAddress: data.RewardAddress.String(), + OwnerAddress: data.OwnerAddress.String(), + ControlAddress: data.ControlAddress.String(), + } +} + +// SetHaltBlockDataResource is JSON representation of TxType 0x0F +type SetHaltBlockDataResource struct { + PubKey string `json:"pub_key"` + Height string `json:"height"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (SetHaltBlockDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.SetHaltBlockData) + + return SetHaltBlockDataResource{ + PubKey: data.PubKey.String(), + Height: strconv.FormatUint(data.Height, 10), + } +} + +// RecreateCoinDataResource is JSON representation of TxType 0x10 +type RecreateCoinDataResource struct { + Name string `json:"name"` + Symbol types.CoinSymbol `json:"symbol"` + InitialAmount string `json:"initial_amount"` + InitialReserve string `json:"initial_reserve"` + ConstantReserveRatio string `json:"constant_reserve_ratio"` + MaxSupply string `json:"max_supply"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (RecreateCoinDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.RecreateCoinData) + + return RecreateCoinDataResource{ + Name: data.Name, + Symbol: data.Symbol, + InitialAmount: data.InitialAmount.String(), + InitialReserve: data.InitialReserve.String(), + ConstantReserveRatio: strconv.Itoa(int(data.ConstantReserveRatio)), + MaxSupply: data.MaxSupply.String(), + } +} + +// EditCoinOwnerDataResource is JSON representation of TxType 0x11 +type EditCoinOwnerDataResource struct { + Symbol types.CoinSymbol `json:"symbol"` + NewOwner types.Address `json:"new_owner"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (EditCoinOwnerDataResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.EditCoinOwnerData) + + return EditCoinOwnerDataResource{ + Symbol: data.Symbol, + NewOwner: data.NewOwner, + } +} + +// EditMultisigResource is JSON representation of TxType 0x12 +type EditMultisigResource struct { + Threshold string `json:"threshold"` + Weights []string `json:"weights"` + Addresses []types.Address `json:"addresses"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (EditMultisigResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.EditMultisigData) + + resource := EditMultisigResource{ + Addresses: data.Addresses, + Threshold: strconv.Itoa(int(data.Threshold)), + } + + resource.Weights = make([]string, 0, len(data.Weights)) + for _, weight := range data.Weights { + resource.Weights = append(resource.Weights, strconv.Itoa(int(weight))) + } + + return resource +} + +// PriceVoteResource is JSON representation of TxType 0x13 +type PriceVoteResource struct { + Price uint32 `json:"price"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (PriceVoteResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.PriceVoteData) + + return PriceVoteResource{ + Price: uint32(data.Price), + } +} + +// EditCandidatePublicKeyResource is JSON representation of TxType 0x14 +type EditCandidatePublicKeyResource struct { + PubKey string `json:"pub_key"` + NewPubKey string `json:"new_pub_key"` +} + +// Transform returns TxDataResource from given txData. Used for JSON encoder. +func (EditCandidatePublicKeyResource) Transform(txData interface{}, context *state.CheckState) TxDataResource { + data := txData.(*transaction.EditCandidatePublicKeyData) + + return EditCandidatePublicKeyResource{ + PubKey: data.PubKey.String(), + NewPubKey: data.NewPubKey.String(), + } +} diff --git a/core/transaction/executor.go b/core/transaction/executor.go index 0bcaad683..148f119a6 100644 --- a/core/transaction/executor.go +++ b/core/transaction/executor.go @@ -3,26 +3,24 @@ package transaction import ( "encoding/json" "fmt" + "math/big" + "strconv" + "sync" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" "github.com/tendermint/tendermint/libs/kv" - "math/big" - "sync" -) - -var ( - CommissionMultiplier = big.NewInt(10e14) ) const ( maxTxLength = 7168 maxPayloadLength = 1024 maxServiceDataLength = 128 - - createCoinGas = 5000 + stdGas = 5000 ) +// Response represents standard response from tx delivery/check type Response struct { Code uint32 `json:"code,omitempty"` Data []byte `json:"data,omitempty"` @@ -34,8 +32,8 @@ type Response struct { GasPrice uint32 `json:"gas_price"` } -func RunTx(context *state.State, - isCheck bool, +// RunTx executes transaction in given context +func RunTx(context state.Interface, rawTx []byte, rewardPool *big.Int, currentBlock uint64, @@ -46,10 +44,7 @@ func RunTx(context *state.State, return Response{ Code: code.TxTooLarge, Log: fmt.Sprintf("TX length is over %d bytes", maxTxLength), - Info: EncodeError(map[string]string{ - "max_tx_length": fmt.Sprintf("%d", maxTxLength), - "got_tx_length": fmt.Sprintf("%d", lenRawTx), - }), + Info: EncodeError(code.NewTxTooLarge(fmt.Sprintf("%d", maxTxLength), fmt.Sprintf("%d", lenRawTx))), } } @@ -58,6 +53,7 @@ func RunTx(context *state.State, return Response{ Code: code.DecodeError, Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), } } @@ -65,20 +61,21 @@ func RunTx(context *state.State, return Response{ Code: code.WrongChainID, Log: "Wrong chain id", - Info: EncodeError(map[string]string{ - "current_chain_id": fmt.Sprintf("%d", types.CurrentChainID), - "got_chain_id": fmt.Sprintf("%d", tx.ChainID), - }), + Info: EncodeError(code.NewWrongChainID(fmt.Sprintf("%d", types.CurrentChainID), fmt.Sprintf("%d", tx.ChainID))), } } - if !context.Coins.Exists(tx.GasCoin) { + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + if !checkState.Coins().Exists(tx.GasCoin) { return Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", tx.GasCoin), - Info: EncodeError(map[string]string{ - "gas_coin": fmt.Sprintf("%d", tx.GasCoin), - }), + Info: EncodeError(code.NewCoinNotExists("", tx.GasCoin.String())), } } @@ -86,10 +83,7 @@ func RunTx(context *state.State, return Response{ Code: code.TooLowGasPrice, Log: fmt.Sprintf("Gas price of tx is too low to be included in mempool. Expected %d", minGasPrice), - Info: EncodeError(map[string]string{ - "min_gas_price": fmt.Sprintf("%d", minGasPrice), - "got_gas_price": fmt.Sprintf("%d", tx.GasPrice), - }), + Info: EncodeError(code.NewTooLowGasPrice(fmt.Sprintf("%d", minGasPrice), fmt.Sprintf("%d", tx.GasPrice))), } } @@ -98,10 +92,7 @@ func RunTx(context *state.State, return Response{ Code: code.TxPayloadTooLarge, Log: fmt.Sprintf("TX payload length is over %d bytes", maxPayloadLength), - Info: EncodeError(map[string]string{ - "max_payload_length": fmt.Sprintf("%d", maxPayloadLength), - "got_payload_length": fmt.Sprintf("%d", lenPayload), - }), + Info: EncodeError(code.NewTxPayloadTooLarge(fmt.Sprintf("%d", maxPayloadLength), fmt.Sprintf("%d", lenPayload))), } } @@ -110,10 +101,7 @@ func RunTx(context *state.State, return Response{ Code: code.TxServiceDataTooLarge, Log: fmt.Sprintf("TX service data length is over %d bytes", maxServiceDataLength), - Info: EncodeError(map[string]string{ - "max_service_data_length": fmt.Sprintf("%d", maxServiceDataLength), - "got_service_data_length": fmt.Sprintf("%d", lenServiceData), - }), + Info: EncodeError(code.NewTxServiceDataTooLarge(fmt.Sprintf("%d", maxServiceDataLength), fmt.Sprintf("%d", lenServiceData))), } } @@ -122,6 +110,7 @@ func RunTx(context *state.State, return Response{ Code: code.DecodeError, Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), } } @@ -130,9 +119,7 @@ func RunTx(context *state.State, return Response{ Code: code.TxFromSenderAlreadyInMempool, Log: fmt.Sprintf("Tx from %s already exists in mempool", sender.String()), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - }), + Info: EncodeError(code.NewTxFromSenderAlreadyInMempool(sender.String(), strconv.Itoa(int(currentBlock)))), } } @@ -142,12 +129,13 @@ func RunTx(context *state.State, // check multi-signature if tx.SignatureType == SigTypeMulti { - multisig := context.Accounts.GetAccount(tx.multisig.Multisig) + multisig := checkState.Accounts().GetAccount(tx.multisig.Multisig) if !multisig.IsMultisig() { return Response{ Code: code.MultisigNotExists, Log: "Multisig does not exists", + Info: EncodeError(code.NewMultisigNotExists(tx.multisig.Multisig.String())), } } @@ -156,26 +144,31 @@ func RunTx(context *state.State, if len(tx.multisig.Signatures) > 32 || len(multisigData.Weights) < len(tx.multisig.Signatures) { return Response{ Code: code.IncorrectMultiSignature, - Log: "Incorrect multi-signature"} + Log: "Incorrect multi-signature", + Info: EncodeError(code.NewIncorrectMultiSignature()), + } } txHash := tx.Hash() - var totalWeight uint + var totalWeight uint32 var usedAccounts = map[types.Address]bool{} for _, sig := range tx.multisig.Signatures { signer, err := RecoverPlain(txHash, sig.R, sig.S, sig.V) - if err != nil { return Response{ Code: code.IncorrectMultiSignature, - Log: "Incorrect multi-signature"} + Log: "Incorrect multi-signature", + Info: EncodeError(code.NewIncorrectMultiSignature()), + } } if usedAccounts[signer] { return Response{ - Code: code.IncorrectMultiSignature, - Log: "Incorrect multi-signature"} + Code: code.DuplicatedAddresses, + Log: "Duplicated multisig addresses", + Info: EncodeError(code.NewDuplicatedAddresses(signer.String())), + } } usedAccounts[signer] = true @@ -184,29 +177,23 @@ func RunTx(context *state.State, if totalWeight < multisigData.Threshold { return Response{ - Code: code.IncorrectMultiSignature, + Code: code.NotEnoughMultisigVotes, Log: fmt.Sprintf("Not enough multisig votes. Needed %d, has %d", multisigData.Threshold, totalWeight), - Info: EncodeError(map[string]string{ - "needed_votes": fmt.Sprintf("%d", multisigData.Threshold), - "got_votes": fmt.Sprintf("%d", totalWeight), - }), + Info: EncodeError(code.NewNotEnoughMultisigVotes(fmt.Sprintf("%d", multisigData.Threshold), fmt.Sprintf("%d", totalWeight))), } } } - if expectedNonce := context.Accounts.GetNonce(sender) + 1; expectedNonce != tx.Nonce { + if expectedNonce := checkState.Accounts().GetNonce(sender) + 1; expectedNonce != tx.Nonce { return Response{ Code: code.WrongNonce, Log: fmt.Sprintf("Unexpected nonce. Expected: %d, got %d.", expectedNonce, tx.Nonce), - Info: EncodeError(map[string]string{ - "expected_nonce": fmt.Sprintf("%d", expectedNonce), - "got_nonce": fmt.Sprintf("%d", tx.Nonce), - }), + Info: EncodeError(code.NewWrongNonce(fmt.Sprintf("%d", expectedNonce), fmt.Sprintf("%d", tx.Nonce))), } } - response := tx.decodedData.Run(tx, context, isCheck, rewardPool, currentBlock) + response := tx.decodedData.Run(tx, context, rewardPool, currentBlock) if response.Code != code.TxFromSenderAlreadyInMempool && response.Code != code.OK { currentMempool.Delete(sender) @@ -214,18 +201,20 @@ func RunTx(context *state.State, response.GasPrice = tx.GasPrice - if tx.Type == TypeCreateCoin { - response.GasUsed = createCoinGas - response.GasWanted = createCoinGas + switch tx.Type { + case TypeCreateCoin, TypeEditCoinOwner, TypeRecreateCoin, TypeEditCandidatePublicKey: + response.GasUsed = stdGas + response.GasWanted = stdGas } return response } -func EncodeError(data map[string]string) string { - marshal, err := json.Marshal(data) +// EncodeError encodes error to json +func EncodeError(data interface{}) string { + marshaled, err := json.Marshal(data) if err != nil { panic(err) } - return string(marshal) + return string(marshaled) } diff --git a/core/transaction/executor_test.go b/core/transaction/executor_test.go index 5c473d4ae..2794ebea9 100644 --- a/core/transaction/executor_test.go +++ b/core/transaction/executor_test.go @@ -2,6 +2,7 @@ package transaction import ( "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" @@ -15,22 +16,26 @@ import ( func TestTooLongTx(t *testing.T) { fakeTx := make([]byte, 10000) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) - + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.TxTooLarge { t.Fatalf("Response code is not correct") } + + checkState(t, cState) } func TestIncorrectTx(t *testing.T) { fakeTx := make([]byte, 1) rand.Read(fakeTx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) - + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.DecodeError { t.Fatalf("Response code is not correct") } + + checkState(t, cState) } func TestTooLongPayloadTx(t *testing.T) { @@ -38,7 +43,7 @@ func TestTooLongPayloadTx(t *testing.T) { rand.Read(payload) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -48,7 +53,7 @@ func TestTooLongPayloadTx(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, Data: encodedData, Payload: payload, @@ -66,11 +71,14 @@ func TestTooLongPayloadTx(t *testing.T) { fakeTx, _ := rlp.EncodeToBytes(tx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.TxPayloadTooLarge { t.Fatalf("Response code is not correct. Expected %d, got %d", code.TxPayloadTooLarge, response.Code) } + + checkState(t, cState) } func TestTooLongServiceDataTx(t *testing.T) { @@ -78,7 +86,7 @@ func TestTooLongServiceDataTx(t *testing.T) { rand.Read(serviceData) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -88,7 +96,7 @@ func TestTooLongServiceDataTx(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, Data: encodedData, ServiceData: serviceData, @@ -105,16 +113,19 @@ func TestTooLongServiceDataTx(t *testing.T) { fakeTx, _ := rlp.EncodeToBytes(tx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.TxServiceDataTooLarge { t.Fatalf("Response code is not correct. Expected %d, got %d", code.TxServiceDataTooLarge, response.Code) } + + checkState(t, cState) } func TestUnexpectedNonceTx(t *testing.T) { txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -124,7 +135,7 @@ func TestUnexpectedNonceTx(t *testing.T) { Nonce: 2, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, Data: encodedData, SignatureType: SigTypeSingle, @@ -140,16 +151,18 @@ func TestUnexpectedNonceTx(t *testing.T) { fakeTx, _ := rlp.EncodeToBytes(tx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) - + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.WrongNonce { t.Fatalf("Response code is not correct. Expected %d, got %d", code.WrongNonce, response.Code) } + + checkState(t, cState) } func TestInvalidSigTx(t *testing.T) { txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -158,7 +171,7 @@ func TestInvalidSigTx(t *testing.T) { tx := Transaction{ Nonce: 1, GasPrice: 1, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), ChainID: types.CurrentChainID, Type: TypeSend, Data: encodedData, @@ -178,16 +191,19 @@ func TestInvalidSigTx(t *testing.T) { fakeTx, _ := rlp.EncodeToBytes(tx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.DecodeError { t.Fatalf("Response code is not correct. Expected %d, got %d", code.DecodeError, response.Code) } + + checkState(t, cState) } func TestNotExistMultiSigTx(t *testing.T) { txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -196,7 +212,7 @@ func TestNotExistMultiSigTx(t *testing.T) { tx := Transaction{ Nonce: 1, GasPrice: 1, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, ChainID: types.CurrentChainID, Data: encodedData, @@ -217,11 +233,14 @@ func TestNotExistMultiSigTx(t *testing.T) { fakeTx, _ := rlp.EncodeToBytes(tx) - response := RunTx(getState(), false, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) + cState := getState() + response := RunTx(cState, fakeTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.MultisigNotExists { t.Fatalf("Response code is not correct. Expected %d, got %d", code.MultisigNotExists, response.Code) } + + checkState(t, cState) } func TestMultiSigTx(t *testing.T) { @@ -229,13 +248,13 @@ func TestMultiSigTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msigAddress := cState.Accounts.CreateMultisig([]uint{1}, []types.Address{addr}, 1, 1) + msigAddress := cState.Accounts.CreateMultisig([]uint32{1}, []types.Address{addr}, 1, accounts.CreateMultisigAddress(addr, 1)) cState.Accounts.AddBalance(msigAddress, coin, helpers.BipToPip(big.NewInt(1000000))) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -244,7 +263,7 @@ func TestMultiSigTx(t *testing.T) { tx := Transaction{ Nonce: 1, GasPrice: 1, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), ChainID: types.CurrentChainID, Type: TypeSend, Data: encodedData, @@ -261,11 +280,13 @@ func TestMultiSigTx(t *testing.T) { txBytes, _ := rlp.EncodeToBytes(tx) - response := RunTx(cState, false, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Error code is not 0. Error: %s", response.Log) } + + checkState(t, cState) } func TestMultiSigDoubleSignTx(t *testing.T) { @@ -273,13 +294,13 @@ func TestMultiSigDoubleSignTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msigAddress := cState.Accounts.CreateMultisig([]uint{1, 1}, []types.Address{addr, {}}, 2, 1) + msigAddress := cState.Accounts.CreateMultisig([]uint32{1, 1}, []types.Address{addr, {}}, 2, accounts.CreateMultisigAddress(addr, 1)) cState.Accounts.AddBalance(msigAddress, coin, helpers.BipToPip(big.NewInt(1000000))) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -288,7 +309,7 @@ func TestMultiSigDoubleSignTx(t *testing.T) { tx := Transaction{ Nonce: 1, GasPrice: 1, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, ChainID: types.CurrentChainID, Data: encodedData, @@ -309,11 +330,13 @@ func TestMultiSigDoubleSignTx(t *testing.T) { txBytes, _ := rlp.EncodeToBytes(tx) - response := RunTx(cState, false, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) - if response.Code != code.IncorrectMultiSignature { - t.Fatalf("Error code is not %d, got %d", code.IncorrectMultiSignature, response.Code) + if response.Code != code.DuplicatedAddresses { + t.Fatalf("Error code is not %d, got %d", code.DuplicatedAddresses, response.Code) } + + checkState(t, cState) } func TestMultiSigTooManySignsTx(t *testing.T) { @@ -321,13 +344,13 @@ func TestMultiSigTooManySignsTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msigAddress := cState.Accounts.CreateMultisig([]uint{1, 1}, []types.Address{addr, {}}, 2, 1) + msigAddress := cState.Accounts.CreateMultisig([]uint32{1, 1}, []types.Address{addr, {}}, 2, accounts.CreateMultisigAddress(addr, 1)) cState.Accounts.AddBalance(msigAddress, coin, helpers.BipToPip(big.NewInt(1000000))) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -336,7 +359,7 @@ func TestMultiSigTooManySignsTx(t *testing.T) { tx := Transaction{ Nonce: 1, GasPrice: 1, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), ChainID: types.CurrentChainID, Type: TypeSend, Data: encodedData, @@ -360,11 +383,13 @@ func TestMultiSigTooManySignsTx(t *testing.T) { txBytes, _ := rlp.EncodeToBytes(tx) - response := RunTx(cState, false, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.IncorrectMultiSignature { t.Fatalf("Error code is not %d, got %d", code.IncorrectMultiSignature, response.Code) } + + checkState(t, cState) } func TestMultiSigNotEnoughTx(t *testing.T) { @@ -372,13 +397,13 @@ func TestMultiSigNotEnoughTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msigAddress := cState.Accounts.CreateMultisig([]uint{1}, []types.Address{addr}, 2, 1) + msigAddress := cState.Accounts.CreateMultisig([]uint32{1}, []types.Address{addr}, 2, accounts.CreateMultisigAddress(addr, 1)) cState.Accounts.AddBalance(msigAddress, coin, helpers.BipToPip(big.NewInt(1000000))) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -388,7 +413,7 @@ func TestMultiSigNotEnoughTx(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, Data: encodedData, SignatureType: SigTypeMulti, @@ -404,11 +429,13 @@ func TestMultiSigNotEnoughTx(t *testing.T) { txBytes, _ := rlp.EncodeToBytes(tx) - response := RunTx(cState, false, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) - if response.Code != code.IncorrectMultiSignature { - t.Fatalf("Error code is not %d. Error: %d", code.IncorrectMultiSignature, response.Code) + if response.Code != code.NotEnoughMultisigVotes { + t.Fatalf("Error code is not %d. Error: %d", code.NotEnoughMultisigVotes, response.Code) } + + checkState(t, cState) } func TestMultiSigIncorrectSignsTx(t *testing.T) { @@ -416,13 +443,13 @@ func TestMultiSigIncorrectSignsTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msigAddress := cState.Accounts.CreateMultisig([]uint{1}, []types.Address{addr}, 1, 1) + msigAddress := cState.Accounts.CreateMultisig([]uint32{1}, []types.Address{addr}, 1, accounts.CreateMultisigAddress(addr, 1)) cState.Accounts.AddBalance(msigAddress, coin, helpers.BipToPip(big.NewInt(1000000))) txData := SendData{ - Coin: types.GetBaseCoin(), + Coin: types.GetBaseCoinID(), To: types.Address{}, Value: big.NewInt(1), } @@ -432,7 +459,7 @@ func TestMultiSigIncorrectSignsTx(t *testing.T) { Nonce: 1, GasPrice: 1, ChainID: types.CurrentChainID, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), Type: TypeSend, Data: encodedData, SignatureType: SigTypeMulti, @@ -449,9 +476,11 @@ func TestMultiSigIncorrectSignsTx(t *testing.T) { txBytes, _ := rlp.EncodeToBytes(tx) - response := RunTx(cState, false, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, txBytes, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.IncorrectMultiSignature { t.Fatalf("Error code is not %d, got %d", code.IncorrectMultiSignature, response.Code) } + + checkState(t, cState) } diff --git a/core/transaction/multisend.go b/core/transaction/multisend.go index a32800838..0d018cc74 100644 --- a/core/transaction/multisend.go +++ b/core/transaction/multisend.go @@ -4,38 +4,29 @@ import ( "encoding/hex" "encoding/json" "fmt" + "math/big" + "sort" + "strings" + "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/kv" - "math/big" - "sort" - "strings" ) type MultisendData struct { List []MultisendDataItem `json:"list"` } -func (data MultisendData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data MultisendData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data MultisendData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { quantity := len(data.List) if quantity < 1 || quantity > 100 { return &Response{ Code: code.InvalidMultisendData, Log: "List length must be between 1 and 100", - Info: EncodeError(map[string]string{ - "code": fmt.Sprintf("%d", code.InvalidMultisendData), - "description": "invalid_multisend_data", - "min_quantity": "1", - "max_quantity": "100", - "got_quantity": fmt.Sprintf("%d", quantity), - }), + Info: EncodeError(code.NewInvalidMultisendData("1", "100", fmt.Sprintf("%d", quantity))), } } @@ -46,7 +37,7 @@ func (data MultisendData) BasicCheck(tx *Transaction, context *state.State) *Res } type MultisendDataItem struct { - Coin types.CoinSymbol + Coin types.CoinID To types.Address Value *big.Int } @@ -64,17 +55,23 @@ func (item MultisendDataItem) MarshalJSON() ([]byte, error) { } func (data MultisendData) String() string { - return fmt.Sprintf("MULTISEND") + return "MULTISEND" } func (data MultisendData) Gas() int64 { return commissions.SendTx + ((int64(len(data.List)) - 1) * commissions.MultisendDelta) } -func (data MultisendData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data MultisendData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -83,44 +80,32 @@ func (data MultisendData) Run(tx *Transaction, context *state.State, isCheck boo commission := big.NewInt(0).Set(commissionInBaseCoin) if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + coin := checkState.Coins().GetCoin(tx.GasCoin) errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) } - if errResp := checkBalances(context, sender, data.List, commission, tx.GasCoin); errResp != nil { + if errResp := checkBalances(checkState, sender, data.List, commission, tx.GasCoin); errResp != nil { return *errResp } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) for _, item := range data.List { - context.Accounts.SubBalance(sender, item.Coin, item.Value) - context.Accounts.AddBalance(item.To, item.Coin, item.Value) + deliverState.Accounts.SubBalance(sender, item.Coin, item.Value) + deliverState.Accounts.AddBalance(item.To, item.Coin, item.Value) } - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ @@ -137,8 +122,8 @@ func (data MultisendData) Run(tx *Transaction, context *state.State, isCheck boo } } -func checkBalances(context *state.State, sender types.Address, items []MultisendDataItem, commission *big.Int, gasCoin types.CoinSymbol) *Response { - total := map[types.CoinSymbol]*big.Int{} +func checkBalances(context *state.CheckState, sender types.Address, items []MultisendDataItem, commission *big.Int, gasCoin types.CoinID) *Response { + total := map[types.CoinID]*big.Int{} total[gasCoin] = big.NewInt(0).Set(commission) for _, item := range items { @@ -149,26 +134,23 @@ func checkBalances(context *state.State, sender types.Address, items []Multisend total[item.Coin].Add(total[item.Coin], item.Value) } - coins := make([]types.CoinSymbol, 0, len(total)) + coins := make([]types.CoinID, 0, len(total)) for k := range total { coins = append(coins, k) } sort.SliceStable(coins, func(i, j int) bool { - return coins[i].Compare(coins[j]) == 1 + return coins[i] > coins[j] }) for _, coin := range coins { value := total[coin] - if context.Accounts.GetBalance(sender, coin).Cmp(value) < 0 { + coinData := context.Coins().GetCoin(coin) + if context.Accounts().GetBalance(sender, coin).Cmp(value) < 0 { return &Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), value, coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": fmt.Sprintf("%d", value), - "coin": fmt.Sprintf("%d", coin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), value, coinData.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), value.String(), coinData.GetFullSymbol(), coinData.ID().String())), } } } @@ -176,15 +158,13 @@ func checkBalances(context *state.State, sender types.Address, items []Multisend return nil } -func checkCoins(context *state.State, items []MultisendDataItem) *Response { +func checkCoins(context *state.CheckState, items []MultisendDataItem) *Response { for _, item := range items { - if !context.Coins.Exists(item.Coin) { + if !context.Coins().Exists(item.Coin) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", item.Coin), - Info: EncodeError(map[string]string{ - "coin": fmt.Sprintf("%s", item.Coin), - }), + Info: EncodeError(code.NewCoinNotExists("", item.Coin.String())), } } } diff --git a/core/transaction/multisend_test.go b/core/transaction/multisend_test.go index d0f7579d2..e981bdf48 100644 --- a/core/transaction/multisend_test.go +++ b/core/transaction/multisend_test.go @@ -1,13 +1,15 @@ package transaction import ( + "math/big" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" ) func TestMultisendTx(t *testing.T) { @@ -15,7 +17,7 @@ func TestMultisendTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -58,7 +60,7 @@ func TestMultisendTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error: %s", response.Log) @@ -75,4 +77,294 @@ func TestMultisendTx(t *testing.T) { if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", to.String(), targetTestBalance, testBalance) } + + checkState(t, cState) +} + +func TestMultisendTxToInvalidDataLength(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + + data := MultisendData{ + List: nil, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + GasCoin: coin, + ChainID: types.CurrentChainID, + Type: TypeMultisend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InvalidMultisendData { + t.Fatalf("Response code is not %d. Error %s", code.InvalidMultisendData, response.Log) + } + + list := make([]MultisendDataItem, 101) + for i := 0; i <= 100; i++ { + list[i] = MultisendDataItem{ + Coin: types.GetBaseCoinID(), + To: [20]byte{1}, + Value: helpers.BipToPip(big.NewInt(10)), + } + } + + data.List = list + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InvalidMultisendData { + t.Fatalf("Response code is not %d. Error %s", code.InvalidMultisendData, response.Log) + } + + checkState(t, cState) +} + +func TestMultisendTxToInsufficientFunds(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := MultisendData{ + List: []MultisendDataItem{ + { + Coin: coin, + To: to, + Value: value, + }, + }, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + GasCoin: coin, + ChainID: types.CurrentChainID, + Type: TypeMultisend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestMultisendToInvalidCoin(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.CoinID(5) + + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(1000000))) + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := MultisendData{ + List: []MultisendDataItem{ + { + Coin: coin, + To: to, + Value: value, + }, + }, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + ChainID: types.CurrentChainID, + Type: TypeMultisend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestMultisendToInsufficientReserve(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.CoinID(5) + + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(1000000))) + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := MultisendData{ + List: []MultisendDataItem{ + { + Coin: coin, + To: to, + Value: value, + }, + }, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + ChainID: types.CurrentChainID, + Type: TypeMultisend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestMultisendTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Commit() + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := MultisendData{ + List: []MultisendDataItem{ + { + Coin: coin, + To: to, + Value: value, + }, + }, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + GasCoin: coin, + ChainID: types.CurrentChainID, + Type: TypeMultisend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/price_vote.go b/core/transaction/price_vote.go new file mode 100644 index 000000000..4b354ae30 --- /dev/null +++ b/core/transaction/price_vote.go @@ -0,0 +1,87 @@ +package transaction + +import ( + "encoding/hex" + "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/formula" + "github.com/tendermint/tendermint/libs/kv" + "math/big" +) + +type PriceVoteData struct { + Price uint +} + +func (data PriceVoteData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + return nil +} + +func (data PriceVoteData) String() string { + return fmt.Sprintf("PRICE VOTE price: %d", data.Price) +} + +func (data PriceVoteData) Gas() int64 { + return commissions.PriceVoteData +} + +func (data PriceVoteData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + + deliverState.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Accounts.SetNonce(sender, tx.Nonce) + } + + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypePriceVote)}))}, + kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, + } + + return Response{ + Code: code.OK, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + Tags: tags, + } +} diff --git a/core/transaction/price_vote_test.go b/core/transaction/price_vote_test.go new file mode 100644 index 000000000..05652ddbf --- /dev/null +++ b/core/transaction/price_vote_test.go @@ -0,0 +1,127 @@ +package transaction + +import ( + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" + "math/big" + "sync" + "testing" +) + +func TestPriceVoteTx(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), big.NewInt(1e18)) + + data := PriceVoteData{Price: 1} + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypePriceVote, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error: %s", response.Log) + } + + checkState(t, cState) +} + +func TestPriceVoteTxToInsufficientFunds(t *testing.T) { + cState := getState() + privateKey, _ := getAccount() + + data := PriceVoteData{Price: 1} + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypePriceVote, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error: %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestPriceVoteTxToCoinReserveUnderflow(t *testing.T) { + cState := getState() + customCoin := createTestCoin(cState) + privateKey, _ := getAccount() + + cState.Coins.SubReserve(customCoin, helpers.BipToPip(big.NewInt(90000))) + + data := PriceVoteData{Price: 1} + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: customCoin, + Type: TypePriceVote, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error: %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} diff --git a/core/transaction/recreate_coin.go b/core/transaction/recreate_coin.go new file mode 100644 index 000000000..4c2f4a87e --- /dev/null +++ b/core/transaction/recreate_coin.go @@ -0,0 +1,213 @@ +package transaction + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" + + "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/kv" +) + +type RecreateCoinData struct { + Name string + Symbol types.CoinSymbol + InitialAmount *big.Int + InitialReserve *big.Int + ConstantReserveRatio uint32 + MaxSupply *big.Int +} + +func (data RecreateCoinData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + if data.InitialReserve == nil || data.InitialAmount == nil || data.MaxSupply == nil { + return &Response{ + Code: code.DecodeError, + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } + } + + if len(data.Name) > maxCoinNameBytes { + return &Response{ + Code: code.InvalidCoinName, + Log: fmt.Sprintf("Coin name is invalid. Allowed up to %d bytes.", maxCoinNameBytes), + Info: EncodeError(code.NewInvalidCoinName(strconv.Itoa(maxCoinNameBytes), strconv.Itoa(len(data.Name)))), + } + } + + if data.ConstantReserveRatio < 10 || data.ConstantReserveRatio > 100 { + return &Response{ + Code: code.WrongCrr, + Log: "Constant Reserve Ratio should be between 10 and 100", + Info: EncodeError(code.NewWrongCrr("10", "100", strconv.Itoa(int(data.ConstantReserveRatio)))), + } + } + + if data.InitialAmount.Cmp(minCoinSupply) == -1 || data.InitialAmount.Cmp(data.MaxSupply) == 1 { + return &Response{ + Code: code.WrongCoinSupply, + Log: fmt.Sprintf("Coin supply should be between %s and %s", minCoinSupply.String(), data.MaxSupply.String()), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } + } + + if data.MaxSupply.Cmp(maxCoinSupply) == 1 { + return &Response{ + Code: code.WrongCoinSupply, + Log: fmt.Sprintf("Max coin supply should be less than %s", maxCoinSupply), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } + } + + if data.InitialReserve.Cmp(minCoinReserve) == -1 { + return &Response{ + Code: code.WrongCoinSupply, + Log: fmt.Sprintf("Coin reserve should be greater than or equal to %s", minCoinReserve.String()), + Info: EncodeError(code.NewWrongCoinSupply(maxCoinSupply.String(), data.MaxSupply.String(), minCoinReserve.String(), data.InitialReserve.String(), minCoinSupply.String(), data.MaxSupply.String(), data.InitialAmount.String())), + } + } + + sender, _ := tx.Sender() + + coin := context.Coins().GetCoinBySymbol(data.Symbol, 0) + if coin == nil { + return &Response{ + Code: code.CoinNotExists, + Log: fmt.Sprintf("Coin %s not exists", data.Symbol), + Info: EncodeError(code.NewCoinNotExists(data.Symbol.String(), "")), + } + } + + symbolInfo := context.Coins().GetSymbolInfo(coin.Symbol()) + if symbolInfo == nil || symbolInfo.OwnerAddress() == nil || *symbolInfo.OwnerAddress() != sender { + var owner *string + if symbolInfo != nil && symbolInfo.OwnerAddress() != nil { + own := symbolInfo.OwnerAddress().String() + owner = &own + } + return &Response{ + Code: code.IsNotOwnerOfCoin, + Log: "Sender is not owner of coin", + Info: EncodeError(code.NewIsNotOwnerOfCoin(data.Symbol.String(), owner)), + } + } + + return nil +} + +func (data RecreateCoinData) String() string { + return fmt.Sprintf("RECREATE COIN symbol:%s reserve:%s amount:%s crr:%d", + data.Symbol.String(), data.InitialReserve, data.InitialAmount, data.ConstantReserveRatio) +} + +func (data RecreateCoinData) Gas() int64 { + return commissions.RecreateCoin +} + +func (data RecreateCoinData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if tx.GasCoin != types.GetBaseCoinID() { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if checkState.Accounts().GetBalance(sender, types.GetBaseCoinID()).Cmp(data.InitialReserve) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), data.InitialReserve.String(), types.GetBaseCoin()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), data.InitialReserve.String(), types.GetBaseCoin().String(), types.GetBaseCoinID().String())), + } + } + + if tx.GasCoin.IsBaseCoin() { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + totalTxCost := big.NewInt(0) + totalTxCost.Add(totalTxCost, data.InitialReserve) + totalTxCost.Add(totalTxCost, commission) + + if checkState.Accounts().GetBalance(sender, types.GetBaseCoinID()).Cmp(totalTxCost) < 0 { + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), totalTxCost.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + } + + var coinId = checkState.App().GetNextCoinID() + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, types.GetBaseCoinID(), data.InitialReserve) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + + deliverState.Coins.Recreate( + coinId, + data.Name, + data.Symbol, + data.InitialAmount, + data.ConstantReserveRatio, + data.InitialReserve, + data.MaxSupply, + ) + + deliverState.App.SetCoinsCount(coinId.Uint32()) + deliverState.Accounts.AddBalance(sender, coinId, data.InitialAmount) + deliverState.Accounts.SetNonce(sender, tx.Nonce) + + } + + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeRecreateCoin)}))}, + kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, + kv.Pair{Key: []byte("tx.coin_symbol"), Value: []byte(data.Symbol.String())}, + kv.Pair{Key: []byte("tx.coin_id"), Value: []byte(coinId.String())}, + } + + return Response{ + Code: code.OK, + Tags: tags, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + } +} diff --git a/core/transaction/recreate_coin_test.go b/core/transaction/recreate_coin_test.go new file mode 100644 index 000000000..7759e6360 --- /dev/null +++ b/core/transaction/recreate_coin_test.go @@ -0,0 +1,661 @@ +package transaction + +import ( + "crypto/ecdsa" + "encoding/binary" + "math/big" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" +) + +func TestRecreateCoinTx(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := createTestCoinWithOwner(cState, addr) + 1 + gasCoin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, gasCoin, helpers.BipToPip(big.NewInt(20000))) + + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + + data := RecreateCoinData{ + Name: "TEST", + Symbol: getTestCoinSymbol(), + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + tx, err := makeTestRecreateCoinTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + err = cState.Coins.Commit() + if err != nil { + t.Fatalf("Commit coins failed. Error %s", err) + } + + targetBalance, _ := big.NewInt(0).SetString("0", 10) + balance := cState.Accounts.GetBalance(addr, gasCoin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %d balance is not correct. Expected %s, got %s", gasCoin, targetBalance, balance) + } + + targetBalance = helpers.BipToPip(big.NewInt(100)) + balance = cState.Accounts.GetBalance(addr, coin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %d balance is not correct. Expected %s, got %s", coin, targetBalance, balance) + } + + newCoinSymbol := getTestCoinSymbol() + stateCoin := cState.Coins.GetCoinBySymbol(newCoinSymbol, 0) + + if stateCoin == nil { + t.Fatalf("Coin %s not found in state", newCoinSymbol) + } + + if stateCoin.Reserve().Cmp(reserve) != 0 { + t.Fatalf("Reserve balance in state is not correct. Expected %s, got %s", reserve, stateCoin.Reserve()) + } + + if stateCoin.Volume().Cmp(amount) != 0 { + t.Fatalf("Volume in state is not correct. Expected %s, got %s", amount, stateCoin.Volume()) + } + + if stateCoin.Crr() != crr { + t.Fatalf("Crr in state is not correct. Expected %d, got %d", crr, stateCoin.Crr()) + } + + if stateCoin.Version() != 0 { + t.Fatalf("Version in state is not correct. Expected %d, got %d", 0, stateCoin.Version()) + } + + if stateCoin.Name() != "TEST" { + t.Fatalf("Name in state is not correct. Expected TEST, got %s", stateCoin.Name()) + } + + stateCoin = cState.Coins.GetCoinBySymbol(newCoinSymbol, 1) + + if stateCoin == nil { + t.Fatalf("Coin %s not found in state", newCoinSymbol) + } + + if stateCoin.Version() != 1 { + t.Fatalf("Version in state is not correct. Expected %d, got %d", 1, stateCoin.Version()) + } + + checkState(t, cState) +} + +func TestRecreateCoinTxWithWrongOwner(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + privateKey2, _ := crypto.GenerateKey() + + createTestCoinWithOwner(cState, addr) + gasCoin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, gasCoin, helpers.BipToPip(big.NewInt(20000))) + + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + + data := RecreateCoinData{ + Symbol: getTestCoinSymbol(), + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + tx, err := makeTestRecreateCoinTx(data, privateKey2) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCoin { + t.Fatalf("Response code is not 206. Error %s", response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinTxWithWrongSymbol(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + createTestCoinWithOwner(cState, addr) + gasCoin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, gasCoin, helpers.BipToPip(big.NewInt(20000))) + + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + + data := RecreateCoinData{ + Symbol: types.StrToCoinSymbol("UNKNOWN"), + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + tx, err := makeTestRecreateCoinTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, tx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not 102. Error %s", response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinWithIncorrectName(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(50) + + var name [65]byte + binary.BigEndian.PutUint64(name[:], 0) + + data := RecreateCoinData{ + Name: string(name[:]), + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InvalidCoinName { + t.Fatalf("Response code is not %d. Error %s", code.InvalidCoinName, response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinWithWrongCrr(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + amount := helpers.BipToPip(big.NewInt(100)) + crr := uint32(9) + name := "My Test Coin" + + data := RecreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: amount, + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: big.NewInt(0).Mul(amount, big.NewInt(10)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCrr { + t.Fatalf("Response code is not %d. Error %s", code.WrongCrr, response.Log) + } + + checkState(t, cState) + + data.ConstantReserveRatio = uint32(101) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx = Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCrr { + t.Fatalf("Response code is not %d. Error %s", code.WrongCrr, response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinWithWrongCoinSupply(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + toCreate := types.StrToCoinSymbol("ABCDEF") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + data := RecreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: big.NewInt(1), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100000)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) + + data.InitialAmount = helpers.BipToPip(big.NewInt(1000000)) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx = Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + data.MaxSupply = big.NewInt(0).Exp(big.NewInt(100), big.NewInt(15+18), nil) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) + + data.MaxSupply = maxCoinSupply + data.InitialReserve = helpers.BipToPip(big.NewInt(1000)) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongCoinSupply { + t.Fatalf("Response code is not %d. Error %s", code.WrongCoinSupply, response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinWithInsufficientFundsForGas(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + createTestCoinWithOwner(cState, crypto.PubkeyToAddress(privateKey.PublicKey)) + cState.Commit() + + coin := types.GetBaseCoinID() + + toCreate := types.StrToCoinSymbol("TEST") + reserve := helpers.BipToPip(big.NewInt(10000)) + crr := uint32(50) + name := "My Test Coin" + + data := RecreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Accounts.SetBalance(addr, types.GetBaseCoinID(), data.InitialReserve) + cState.Commit() + + tx.GasCoin = types.GetBaseCoinID() + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinToInsufficientFundsForInitialReserve(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + createTestCoinWithOwner(cState, crypto.PubkeyToAddress(privateKey.PublicKey)) + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + cState.Accounts.SetBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10000))) + cState.Commit() + + toCreate := types.StrToCoinSymbol("TEST") + reserve := helpers.BipToPip(big.NewInt(100000)) + crr := uint32(50) + name := "My Test Coin" + + data := RecreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedTx, err := makeTestRecreateCoinTx(data, privateKey) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestRecreateCoinToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := createTestCoinWithOwner(cState, crypto.PubkeyToAddress(privateKey.PublicKey)) + + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(105))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(105))) + cState.Accounts.AddBalance(addr, types.GetBaseCoinID(), helpers.BipToPip(big.NewInt(10000))) + cState.Commit() + + toCreate := types.StrToCoinSymbol("TEST") + reserve := helpers.BipToPip(big.NewInt(100000)) + crr := uint32(50) + name := "My Test Coin" + + data := RecreateCoinData{ + Name: name, + Symbol: toCreate, + InitialAmount: helpers.BipToPip(big.NewInt(10)), + InitialReserve: reserve, + ConstantReserveRatio: crr, + MaxSupply: helpers.BipToPip(big.NewInt(100)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func makeTestRecreateCoinTx(data RecreateCoinData, privateKey *ecdsa.PrivateKey) ([]byte, error) { + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + return nil, err + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeRecreateCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + return nil, err + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + return nil, err + } + + return encodedTx, nil +} diff --git a/core/transaction/redeem_check.go b/core/transaction/redeem_check.go index 4d17b184c..ff5c406a8 100644 --- a/core/transaction/redeem_check.go +++ b/core/transaction/redeem_check.go @@ -2,21 +2,21 @@ package transaction import ( "bytes" - "encoding/base64" "encoding/hex" - "encoding/json" "fmt" + "math/big" + "strconv" + "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/kv" - "math/big" + "golang.org/x/crypto/sha3" ) type RedeemCheckData struct { @@ -24,36 +24,22 @@ type RedeemCheckData struct { Proof [65]byte } -func (data RedeemCheckData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - RawCheck string `json:"raw_check"` - Proof string `json:"proof"` - }{ - RawCheck: base64.StdEncoding.EncodeToString(data.RawCheck), - Proof: base64.StdEncoding.EncodeToString(data.Proof[:]), - }) -} - -func (data RedeemCheckData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data RedeemCheckData) CommissionInBaseCoin(tx *Transaction) *big.Int { - panic("implement me") -} - -func (data RedeemCheckData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data RedeemCheckData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.RawCheck == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } // fixed potential problem with making too high commission for sender if tx.GasPrice != 1 { return &Response{ Code: code.TooHighGasPrice, - Log: fmt.Sprintf("Gas price for check is limited to 1")} + Log: "Gas price for check is limited to 1", + Info: EncodeError(code.NewTooHighGasPrice("1", strconv.Itoa(int(tx.GasPrice)))), + } } return nil @@ -67,10 +53,16 @@ func (data RedeemCheckData) Gas() int64 { return commissions.RedeemCheckTx } -func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data RedeemCheckData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -80,6 +72,7 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.DecodeError, Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), } } @@ -87,10 +80,7 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.WrongChainID, Log: "Wrong chain id", - Info: EncodeError(map[string]string{ - "current_chain_id": fmt.Sprintf("%d", types.CurrentChainID), - "got_chain_id": fmt.Sprintf("%d", decodedCheck.ChainID), - }), + Info: EncodeError(code.NewWrongChainID(fmt.Sprintf("%d", types.CurrentChainID), fmt.Sprintf("%d", tx.ChainID))), } } @@ -98,6 +88,7 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.TooLongNonce, Log: "Nonce is too big. Should be up to 16 bytes.", + Info: EncodeError(code.NewTooLongNonce(strconv.Itoa(len(decodedCheck.Nonce)), "16")), } } @@ -106,26 +97,24 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b if err != nil { return Response{ Code: code.DecodeError, - Log: err.Error()} + Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), + } } - if !context.Coins.Exists(decodedCheck.Coin) { + if !checkState.Coins().Exists(decodedCheck.Coin) { return Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists"), - Info: EncodeError(map[string]string{ - "coin": fmt.Sprintf("%s", decodedCheck.Coin), - }), + Log: "Coin not exists", + Info: EncodeError(code.NewCoinNotExists("", decodedCheck.Coin.String())), } } - if !context.Coins.Exists(decodedCheck.GasCoin) { + if !checkState.Coins().Exists(decodedCheck.GasCoin) { return Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Gas coin not exists"), - Info: EncodeError(map[string]string{ - "gas_coin": fmt.Sprintf("%s", decodedCheck.GasCoin), - }), + Log: "Gas coin not exists", + Info: EncodeError(code.NewCoinNotExists("", decodedCheck.GasCoin.String())), } } @@ -133,27 +122,24 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.WrongGasCoin, Log: fmt.Sprintf("Gas coin for redeem check transaction can only be %s", decodedCheck.GasCoin), - Info: EncodeError(map[string]string{ - "gas_coin": fmt.Sprintf("%s", decodedCheck.GasCoin), - }), + Info: EncodeError(code.NewWrongGasCoin(checkState.Coins().GetCoin(tx.GasCoin).GetFullSymbol(), tx.GasCoin.String(), checkState.Coins().GetCoin(decodedCheck.GasCoin).GetFullSymbol(), decodedCheck.GasCoin.String())), } } if decodedCheck.DueBlock < currentBlock { return Response{ Code: code.CheckExpired, - Log: fmt.Sprintf("Check expired"), - Info: EncodeError(map[string]string{ - "due_block": fmt.Sprintf("%d", decodedCheck.DueBlock), - "current_block": fmt.Sprintf("%d", currentBlock), - }), + Log: "Check expired", + Info: EncodeError(code.MewCheckExpired(fmt.Sprintf("%d", decodedCheck.DueBlock), fmt.Sprintf("%d", currentBlock))), } } - if context.Checks.IsCheckUsed(decodedCheck) { + if checkState.Checks().IsCheckUsed(decodedCheck) { return Response{ Code: code.CheckUsed, - Log: fmt.Sprintf("Check already redeemed")} + Log: "Check already redeemed", + Info: EncodeError(code.NewCheckUsed()), + } } lockPublicKey, err := decodedCheck.LockPubKey() @@ -162,11 +148,12 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.DecodeError, Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), } } var senderAddressHash types.Hash - hw := sha3.NewKeccak256() + hw := sha3.NewLegacyKeccak256() _ = rlp.Encode(hw, []interface{}{ sender, }) @@ -178,6 +165,7 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.DecodeError, Log: err.Error(), + Info: EncodeError(code.NewDecodeError()), } } @@ -185,79 +173,69 @@ func (data RedeemCheckData) Run(tx *Transaction, context *state.State, isCheck b return Response{ Code: code.CheckInvalidLock, Log: "Invalid proof", + Info: EncodeError(code.NewCheckInvalidLock()), } } - commissionInBaseCoin := big.NewInt(0).Mul(big.NewInt(int64(tx.GasPrice)), big.NewInt(tx.Gas())) - commissionInBaseCoin.Mul(commissionInBaseCoin, CommissionMultiplier) + commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) + gasCoin := checkState.Coins().GetCoin(decodedCheck.GasCoin) + coin := checkState.Coins().GetCoin(decodedCheck.Coin) + if !decodedCheck.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(decodedCheck.GasCoin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } if decodedCheck.Coin == decodedCheck.GasCoin { totalTxCost := big.NewInt(0).Add(decodedCheck.Value, commission) - if context.Accounts.GetBalance(checkSender, decodedCheck.Coin).Cmp(totalTxCost) < 0 { + if checkState.Accounts().GetBalance(checkSender, decodedCheck.Coin).Cmp(totalTxCost) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", decodedCheck.Coin, checkSender.String(), totalTxCost.String(), decodedCheck.Coin), - Info: EncodeError(map[string]string{ - "sender": checkSender.String(), - "coin": fmt.Sprintf("%s", decodedCheck.Coin), - "total_tx_cost": totalTxCost.String(), - }), + Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", decodedCheck.Coin, checkSender.String(), totalTxCost.String(), coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), totalTxCost.String(), coin.GetFullSymbol(), coin.ID().String())), } } } else { - if context.Accounts.GetBalance(checkSender, decodedCheck.Coin).Cmp(decodedCheck.Value) < 0 { + if checkState.Accounts().GetBalance(checkSender, decodedCheck.Coin).Cmp(decodedCheck.Value) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", checkSender.String(), decodedCheck.Coin, decodedCheck.Value.String(), decodedCheck.Coin), - Info: EncodeError(map[string]string{ - "sender": checkSender.String(), - "coin": fmt.Sprintf("%s", decodedCheck.Coin), - "value": decodedCheck.Value.String(), - }), + Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", checkSender.String(), decodedCheck.Coin, decodedCheck.Value.String(), coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(checkSender.String(), decodedCheck.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } - if context.Accounts.GetBalance(checkSender, decodedCheck.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(checkSender, decodedCheck.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", checkSender.String(), decodedCheck.GasCoin, commission.String(), decodedCheck.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "gas_coin": fmt.Sprintf("%s", decodedCheck.GasCoin), - "commission": commission.String(), - }), + Log: fmt.Sprintf("Insufficient funds for check issuer account: %s %s. Wanted %s %s", checkSender.String(), decodedCheck.GasCoin, commission.String(), gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } } - if !isCheck { - context.Checks.UseCheck(decodedCheck) + if deliverState, ok := context.(*state.State); ok { + deliverState.Checks.UseCheck(decodedCheck) rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubVolume(decodedCheck.GasCoin, commission) - context.Coins.SubReserve(decodedCheck.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(decodedCheck.GasCoin, commission) + deliverState.Coins.SubReserve(decodedCheck.GasCoin, commissionInBaseCoin) - context.Accounts.SubBalance(checkSender, decodedCheck.GasCoin, commission) - context.Accounts.SubBalance(checkSender, decodedCheck.Coin, decodedCheck.Value) - context.Accounts.AddBalance(sender, decodedCheck.Coin, decodedCheck.Value) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(checkSender, decodedCheck.GasCoin, commission) + deliverState.Accounts.SubBalance(checkSender, decodedCheck.Coin, decodedCheck.Value) + deliverState.Accounts.AddBalance(sender, decodedCheck.Coin, decodedCheck.Value) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeRedeemCheck)}))}, kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(checkSender[:]))}, kv.Pair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(sender[:]))}, - kv.Pair{Key: []byte("tx.coin"), Value: []byte(decodedCheck.Coin.String())}, + kv.Pair{Key: []byte("tx.coin_id"), Value: []byte(decodedCheck.Coin.String())}, } return Response{ diff --git a/core/transaction/redeem_check_test.go b/core/transaction/redeem_check_test.go index fcbc4fb10..bd422bae7 100644 --- a/core/transaction/redeem_check_test.go +++ b/core/transaction/redeem_check_test.go @@ -2,20 +2,23 @@ package transaction import ( "crypto/sha256" + "math/big" + "sync" + "testing" + c "github.com/MinterTeam/minter-go-node/core/check" + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" - "github.com/MinterTeam/minter-go-node/crypto/sha3" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" + "golang.org/x/crypto/sha3" ) func TestRedeemCheckTx(t *testing.T) { cState := getState() - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() senderPrivateKey, _ := crypto.GenerateKey() senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) @@ -40,7 +43,7 @@ func TestRedeemCheckTx(t *testing.T) { DueBlock: 1, Coin: coin, Value: checkValue, - GasCoin: types.GetBaseCoin(), + GasCoin: types.GetBaseCoinID(), } lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) @@ -60,7 +63,7 @@ func TestRedeemCheckTx(t *testing.T) { rawCheck, _ := rlp.EncodeToBytes(check) var senderAddressHash types.Hash - hw := sha3.NewKeccak256() + hw := sha3.NewLegacyKeccak256() _ = rlp.Encode(hw, []interface{}{ receiverAddr, }) @@ -105,7 +108,7 @@ func TestRedeemCheckTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -115,4 +118,1091 @@ func TestRedeemCheckTx(t *testing.T) { if balance.Cmp(checkValue) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, checkValue, balance) } + + checkState(t, cState) +} + +func TestRedeemCheckTxToDecodeError(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + + if err != nil { + t.Fatal(err) + } + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + response := data.BasicCheck(&tx, state.NewCheckState(cState)) + if response.Code != code.DecodeError { + t.Fatalf("Response code is not %d. Error %s", code.DecodeError, response.Log) + } + + data.RawCheck = []byte{0} + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + txResponse := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if txResponse.Code != code.DecodeError { + t.Fatalf("Response code is not %d. Error %s", code.DecodeError, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToHighGasPrice(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 2, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.TooHighGasPrice { + t.Fatalf("Response code is not %d. Error %s", code.TooHighGasPrice, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToWrongChainID(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.ChainTestnet, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongChainID { + t.Fatalf("Response code is not %d. Error %s", code.WrongChainID, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToNonceLength(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.TooLongNonce { + t.Fatalf("Response code is not %d. Error %s", code.TooLongNonce, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToCheckData(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: 5, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) + + check.Coin = coin + check.GasCoin = 5 + lock, err = crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ = rlp.EncodeToBytes(check) + data = RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) + + check.GasCoin = coin + lock, err = crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ = rlp.EncodeToBytes(check) + data.RawCheck = rawCheck + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + customCoin := createTestCoin(cState) + tx.GasCoin = customCoin + tx.Data = encodedData + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.WrongGasCoin { + t.Fatalf("Response code is not %d. Error %s", code.WrongGasCoin, response.Log) + } + + checkState(t, cState) + + check.DueBlock = 1 + lock, err = crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ = rlp.EncodeToBytes(check) + data.RawCheck = rawCheck + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.GasCoin = coin + tx.Data = encodedData + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 100, &sync.Map{}, 0) + if response.Code != code.CheckExpired { + t.Fatalf("Response code is not %d. Error %s", code.CheckExpired, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToUsed(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + senderAddr := crypto.PubkeyToAddress(senderPrivateKey.PublicKey) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(1000000))) + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + cState.Checks.UseCheck(&check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CheckUsed { + t.Fatalf("Response code is not %d. Error %s", code.CheckUsed, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToInsufficientFunds(t *testing.T) { + cState := getState() + coin := types.GetBaseCoinID() + + senderPrivateKey, _ := crypto.GenerateKey() + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToCoinReserveUnderflow(t *testing.T) { + cState := getState() + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + + senderPrivateKey, _ := crypto.GenerateKey() + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: coin, + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToInsufficientFundsForCheckCoin(t *testing.T) { + cState := getState() + coin := createTestCoin(cState) + + senderPrivateKey, _ := crypto.GenerateKey() + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestRedeemCheckTxToInsufficientFundsForCheckGasCoin(t *testing.T) { + cState := getState() + coin := createTestCoin(cState) + + senderPrivateKey, senderAddr := getAccount() + + receiverPrivateKey, _ := crypto.GenerateKey() + receiverAddr := crypto.PubkeyToAddress(receiverPrivateKey.PublicKey) + + passphrase := "password" + passphraseHash := sha256.Sum256([]byte(passphrase)) + passphrasePk, err := crypto.ToECDSA(passphraseHash[:]) + + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(100))) + cState.Accounts.AddBalance(senderAddr, coin, helpers.BipToPip(big.NewInt(100))) + + if err != nil { + t.Fatal(err) + } + + checkValue := helpers.BipToPip(big.NewInt(10)) + + check := c.Check{ + Nonce: []byte{1, 2, 3}, + ChainID: types.CurrentChainID, + DueBlock: 1, + Coin: coin, + Value: checkValue, + GasCoin: types.GetBaseCoinID(), + } + + lock, err := crypto.Sign(check.HashWithoutLock().Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + check.Lock = big.NewInt(0).SetBytes(lock) + + err = check.Sign(senderPrivateKey) + if err != nil { + t.Fatal(err) + } + + rawCheck, _ := rlp.EncodeToBytes(check) + + var senderAddressHash types.Hash + hw := sha3.NewLegacyKeccak256() + _ = rlp.Encode(hw, []interface{}{ + receiverAddr, + }) + hw.Sum(senderAddressHash[:0]) + + sig, err := crypto.Sign(senderAddressHash.Bytes(), passphrasePk) + if err != nil { + t.Fatal(err) + } + + proof := [65]byte{} + copy(proof[:], sig) + + data := RedeemCheckData{ + RawCheck: rawCheck, + Proof: proof, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeRedeemCheck, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(receiverPrivateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/sell_all_coin.go b/core/transaction/sell_all_coin.go index 74653dd3d..121b60066 100644 --- a/core/transaction/sell_all_coin.go +++ b/core/transaction/sell_all_coin.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -14,31 +13,19 @@ import ( ) type SellAllCoinData struct { - CoinToSell types.CoinSymbol - CoinToBuy types.CoinSymbol + CoinToSell types.CoinID + CoinToBuy types.CoinID MinimumValueToBuy *big.Int } -func (data SellAllCoinData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - CoinToSell string `json:"coin_to_sell"` - CoinToBuy string `json:"coin_to_buy"` - MinimumValueToBuy string `json:"minimum_value_to_buy"` - }{ - CoinToSell: data.CoinToSell.String(), - CoinToBuy: data.CoinToBuy.String(), - MinimumValueToBuy: data.MinimumValueToBuy.String(), - }) -} - -func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { +func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.CheckState) (TotalSpends, []Conversion, *big.Int, *Response) { sender, _ := tx.Sender() total := TotalSpends{} var conversions []Conversion commissionInBaseCoin := tx.CommissionInBaseCoin() - available := context.Accounts.GetBalance(sender, data.CoinToSell) + available := context.Accounts().GetBalance(sender, data.CoinToSell) var value *big.Int total.Add(data.CoinToSell, available) @@ -48,21 +35,18 @@ func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (T amountToSell := big.NewInt(0).Set(available) amountToSell.Sub(amountToSell, commissionInBaseCoin) - coin := context.Coins.GetCoin(data.CoinToBuy) + coin := context.Coins().GetCoin(data.CoinToBuy) value = formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), amountToSell) if value.Cmp(data.MinimumValueToBuy) == -1 { return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), value.String()), - Info: EncodeError(map[string]string{ - "minimum_value_to_buy": data.MinimumValueToBuy.String(), - "coin": value.String(), - }), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), value.String(), coin.GetFullSymbol(), coin.ID().String())), } } - if errResp := CheckForCoinSupplyOverflow(coin.Volume(), value, coin.MaxSupply()); errResp != nil { + if errResp := CheckForCoinSupplyOverflow(coin, value); errResp != nil { return nil, nil, nil, errResp } @@ -75,24 +59,22 @@ func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (T case data.CoinToBuy.IsBaseCoin(): amountToSell := big.NewInt(0).Set(available) - coin := context.Coins.GetCoin(data.CoinToSell) + coin := context.Coins().GetCoin(data.CoinToSell) ret := formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), amountToSell) if ret.Cmp(data.MinimumValueToBuy) == -1 { return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), ret.String()), - Info: EncodeError(map[string]string{ - "minimum_value_to_buy": data.MinimumValueToBuy.String(), - "will_get_value": ret.String(), - }), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), ret.String(), coin.GetFullSymbol(), coin.ID().String())), } } if ret.Cmp(commissionInBaseCoin) == -1 { return nil, nil, nil, &Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account"), + Log: "Insufficient funds for sender account", + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commissionInBaseCoin.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -108,14 +90,15 @@ func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (T default: amountToSell := big.NewInt(0).Set(available) - coinFrom := context.Coins.GetCoin(data.CoinToSell) - coinTo := context.Coins.GetCoin(data.CoinToBuy) + coinFrom := context.Coins().GetCoin(data.CoinToSell) + coinTo := context.Coins().GetCoin(data.CoinToBuy) basecoinValue := formula.CalculateSaleReturn(coinFrom.Volume(), coinFrom.Reserve(), coinFrom.Crr(), amountToSell) if basecoinValue.Cmp(commissionInBaseCoin) == -1 { return nil, nil, nil, &Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account"), + Log: "Insufficient funds for sender account", + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commissionInBaseCoin.String(), coinFrom.GetFullSymbol(), coinFrom.ID().String())), } } @@ -126,14 +109,11 @@ func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (T return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), value.String()), - Info: EncodeError(map[string]string{ - "minimum_value_to_buy": data.MinimumValueToBuy.String(), - "will_get_value": value.String(), - }), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), value.String(), coinTo.GetFullSymbol(), coinTo.ID().String())), } } - if errResp := CheckForCoinSupplyOverflow(coinTo.Volume(), value, coinTo.MaxSupply()); errResp != nil { + if errResp := CheckForCoinSupplyOverflow(coinTo, value); errResp != nil { return nil, nil, nil, errResp } @@ -150,35 +130,33 @@ func (data SellAllCoinData) TotalSpend(tx *Transaction, context *state.State) (T return total, conversions, value, nil } -func (data SellAllCoinData) BasicCheck(tx *Transaction, context *state.State) *Response { - if data.CoinToSell == data.CoinToBuy { +func (data SellAllCoinData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + if !context.Coins().Exists(data.CoinToSell) { return &Response{ - Code: code.CrossConvert, - Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin"), - Info: EncodeError(map[string]string{ - "coin_to_sell": fmt.Sprintf("%s", data.CoinToSell), - "coin_to_buy": fmt.Sprintf("%s", data.CoinToBuy), - }), + Code: code.CoinNotExists, + Log: "Coin to sell not exists", + Info: EncodeError(code.NewCoinNotExists("", data.CoinToSell.String())), } } - if !context.Coins.Exists(data.CoinToSell) { + if !context.Coins().Exists(data.CoinToBuy) { return &Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin to sell not exists"), - Info: EncodeError(map[string]string{ - "coin_to_sell": fmt.Sprintf("%s", data.CoinToSell), - }), + Log: "Coin to buy not exists", + Info: EncodeError(code.NewCoinNotExists("", data.CoinToBuy.String())), } } - if !context.Coins.Exists(data.CoinToBuy) { + if data.CoinToSell == data.CoinToBuy { return &Response{ - Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin to buy not exists"), - Info: EncodeError(map[string]string{ - "coin_to_buy": fmt.Sprintf("%s", data.CoinToBuy), - }), + Code: code.CrossConvert, + Log: "\"From\" coin equals to \"to\" coin", + Info: EncodeError(code.NewCrossConvert( + data.CoinToSell.String(), + context.Coins().GetCoin(data.CoinToSell).GetFullSymbol(), + data.CoinToBuy.String(), + context.Coins().GetCoin(data.CoinToBuy).GetFullSymbol()), + ), } } @@ -194,59 +172,61 @@ func (data SellAllCoinData) Gas() int64 { return commissions.ConvertTx } -func (data SellAllCoinData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data SellAllCoinData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + response := data.BasicCheck(tx, checkState) if response != nil { return *response } - available := context.Accounts.GetBalance(sender, data.CoinToSell) + available := checkState.Accounts().GetBalance(sender, data.CoinToSell) - totalSpends, conversions, value, response := data.TotalSpend(tx, context) + totalSpends, conversions, value, response := data.TotalSpend(tx, checkState) if response != nil { return *response } for _, ts := range totalSpends { - if context.Accounts.GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + if checkState.Accounts().GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + coin := checkState.Coins().GetCoin(ts.Coin) + return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s.", sender.String(), ts.Value.String(), - ts.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": ts.Value.String(), - "coin": fmt.Sprintf("%s", ts.Coin), - }), + coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), ts.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } } - errResp := checkConversionsReserveUnderflow(conversions, context) + errResp := checkConversionsReserveUnderflow(conversions, checkState) if errResp != nil { return *errResp } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { for _, ts := range totalSpends { - context.Accounts.SubBalance(sender, ts.Coin, ts.Value) + deliverState.Accounts.SubBalance(sender, ts.Coin, ts.Value) } for _, conversion := range conversions { - context.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) - context.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) + deliverState.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) + deliverState.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) - context.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) - context.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) + deliverState.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) + deliverState.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) } rewardPool.Add(rewardPool, tx.CommissionInBaseCoin()) - context.Accounts.AddBalance(sender, data.CoinToBuy, value) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.AddBalance(sender, data.CoinToBuy, value) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ diff --git a/core/transaction/sell_all_coin_test.go b/core/transaction/sell_all_coin_test.go index 0930828fc..810c5674b 100644 --- a/core/transaction/sell_all_coin_test.go +++ b/core/transaction/sell_all_coin_test.go @@ -1,30 +1,32 @@ package transaction import ( + "math/big" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" ) func TestSellAllCoinTx(t *testing.T) { cState := getState() - createTestCoin(cState) + coinID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) minValToBuy, _ := big.NewInt(0).SetString("151191152412701306252", 10) data := SellAllCoinData{ CoinToSell: coin, - CoinToBuy: getTestCoinSymbol(), + CoinToBuy: coinID, MinimumValueToBuy: minValToBuy, } @@ -54,7 +56,7 @@ func TestSellAllCoinTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) @@ -66,8 +68,475 @@ func TestSellAllCoinTx(t *testing.T) { } targetTestBalance, _ := big.NewInt(0).SetString("27098160365576186275223", 10) - testBalance := cState.Accounts.GetBalance(addr, getTestCoinSymbol()) + testBalance := cState.Accounts.GetBalance(addr, coinID) if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", getTestCoinSymbol(), targetTestBalance, testBalance) } + + checkState(t, cState) +} + +func TestSellAllCoinTxWithSameCoins(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + minValToBuy, _ := big.NewInt(0).SetString("151191152412701306252", 10) + data := SellAllCoinData{ + CoinToSell: coin, + CoinToBuy: coin, + MinimumValueToBuy: minValToBuy, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CrossConvert { + t.Fatalf("Response code is not %d. Error %s", code.CrossConvert, response.Log) + } + + checkState(t, cState) +} + +func TestSellAllCoinTxWithInvalidCoins(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := types.CoinID(5) + + minValToBuy, _ := big.NewInt(0).SetString("151191152412701306252", 10) + data := SellAllCoinData{ + CoinToSell: coin, + CoinToBuy: types.GetBaseCoinID(), + MinimumValueToBuy: minValToBuy, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) + + data.CoinToSell = types.GetBaseCoinID() + data.CoinToBuy = types.CoinID(5) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestSellAllCoinTxWithMinimumValueToBuy(t *testing.T) { + cState := getState() + coinID := createTestCoin(cState) + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + + minValToBuy, _ := big.NewInt(0).SetString("151191152412701306252", 10) + data := SellAllCoinData{ + CoinToSell: coin, + CoinToBuy: coinID, + MinimumValueToBuy: minValToBuy, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) +} + +func TestSellAllCoinTxWithInsufficientFunds(t *testing.T) { + cState := getState() + coinID := createTestCoin(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + minValToBuy, _ := big.NewInt(0).SetString("0", 10) + data := SellAllCoinData{ + CoinToSell: coinID, + CoinToBuy: coin, + MinimumValueToBuy: minValToBuy, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + cState.Accounts.AddBalance(addr, coinToSellID, big.NewInt(1)) + + data.CoinToBuy = coinID + data.CoinToSell = coinToSellID + data.MinimumValueToBuy = big.NewInt(9e18) + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestSellAllCoinTxToCoinSupplyOverflow(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, sellCoinID, helpers.BipToPip(big.NewInt(100))) + + coinToBuy := cState.Coins.GetCoin(coinToBuyID) + coinToBuy.CMaxSupply = big.NewInt(1) + + data := SellAllCoinData{ + CoinToSell: sellCoinID, + CoinToBuy: coinToBuyID, + MinimumValueToBuy: big.NewInt(0), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: data.CoinToSell, + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinSupplyOverflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinSupplyOverflow, response.Log) + } + + checkState(t, cState) + + // custom buy and sell coins + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + cState.Accounts.AddBalance(addr, coinToSellID, helpers.BipToPip(big.NewInt(90))) + + data.CoinToBuy = coinToBuyID + data.CoinToSell = coinToSellID + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx.GasCoin = coinToSellID + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinSupplyOverflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinSupplyOverflow, response.Log) + } + + checkState(t, cState) +} + +func TestSellAllCoinTxToMinimumValueToBuyReached(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, sellCoinID, helpers.BipToPip(big.NewInt(2))) + + data := SellAllCoinData{ + CoinToBuy: coinToBuyID, + CoinToSell: sellCoinID, + MinimumValueToBuy: big.NewInt(9e18), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx := &Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: sellCoinID, + Type: TypeSellAllCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + decodedData: data, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) + + // coin to buy == base coin + + cState.Accounts.AddBalance(addr, coinToBuyID, big.NewInt(1)) + + data.CoinToBuy = sellCoinID + data.CoinToSell = coinToBuyID + data.MinimumValueToBuy = big.NewInt(9e18) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) + + // custom buy and sell coins + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + cState.Accounts.AddBalance(addr, coinToSellID, big.NewInt(1e17)) + + data.CoinToBuy = coinToBuyID + data.CoinToSell = coinToSellID + data.MinimumValueToBuy = big.NewInt(9e18) + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/sell_coin.go b/core/transaction/sell_coin.go index 56b6ebfbd..f093ed68c 100644 --- a/core/transaction/sell_coin.go +++ b/core/transaction/sell_coin.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -14,27 +13,13 @@ import ( ) type SellCoinData struct { - CoinToSell types.CoinSymbol + CoinToSell types.CoinID ValueToSell *big.Int - CoinToBuy types.CoinSymbol + CoinToBuy types.CoinID MinimumValueToBuy *big.Int } -func (data SellCoinData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - CoinToSell string `json:"coin_to_sell"` - ValueToSell string `json:"value_to_sell"` - CoinToBuy string `json:"coin_to_buy"` - MinimumValueToBuy string `json:"minimum_value_to_buy"` - }{ - CoinToSell: data.CoinToSell.String(), - ValueToSell: data.ValueToSell.String(), - CoinToBuy: data.CoinToBuy.String(), - MinimumValueToBuy: data.MinimumValueToBuy.String(), - }) -} - -func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { +func (data SellCoinData) TotalSpend(tx *Transaction, context *state.CheckState) (TotalSpends, []Conversion, *big.Int, *Response) { total := TotalSpends{} var conversions []Conversion @@ -45,12 +30,13 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota switch { case data.CoinToSell.IsBaseCoin(): - coin := context.Coins.GetCoin(data.CoinToBuy) + coin := context.Coins().GetCoin(data.CoinToBuy) value = formula.CalculatePurchaseReturn(coin.Volume(), coin.Reserve(), coin.Crr(), data.ValueToSell) if value.Cmp(data.MinimumValueToBuy) == -1 { return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), value.String()), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), value.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -70,11 +56,11 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } - if errResp := CheckForCoinSupplyOverflow(coin.Volume(), value, coin.MaxSupply()); errResp != nil { + if errResp := CheckForCoinSupplyOverflow(coin, value); errResp != nil { return nil, nil, nil, errResp } @@ -86,17 +72,14 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota ToReserve: data.ValueToSell, }) case data.CoinToBuy.IsBaseCoin(): - coin := context.Coins.GetCoin(data.CoinToSell) + coin := context.Coins().GetCoin(data.CoinToSell) value = formula.CalculateSaleReturn(coin.Volume(), coin.Reserve(), coin.Crr(), data.ValueToSell) if value.Cmp(data.MinimumValueToBuy) == -1 { return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), value.String()), - Info: EncodeError(map[string]string{ - "minimum_value_to_buy": data.MinimumValueToBuy.String(), - "value_to_buy": value.String(), - }), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), value.String(), coin.GetFullSymbol(), coin.ID().String())), } } @@ -120,11 +103,12 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coin.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coin.GetFullSymbol(), + coin.ID().String(), + coin.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -135,7 +119,7 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota FromCoin: tx.GasCoin, FromAmount: c, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -147,8 +131,8 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota ToCoin: data.CoinToBuy, }) default: - coinFrom := context.Coins.GetCoin(data.CoinToSell) - coinTo := context.Coins.GetCoin(data.CoinToBuy) + coinFrom := context.Coins().GetCoin(data.CoinToSell) + coinTo := context.Coins().GetCoin(data.CoinToBuy) valueToSell := big.NewInt(0).Set(data.ValueToSell) @@ -171,11 +155,12 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has": coinFrom.Reserve().String(), - "required": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coinFrom.GetFullSymbol(), + coinFrom.ID().String(), + coinFrom.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -186,7 +171,7 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota FromCoin: tx.GasCoin, FromAmount: c, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -196,10 +181,7 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota return nil, nil, nil, &Response{ Code: code.MinimumValueToBuyReached, Log: fmt.Sprintf("You wanted to get minimum %s, but currently you will get %s", data.MinimumValueToBuy.String(), value.String()), - Info: EncodeError(map[string]string{ - "minimum_value_to_buy": data.MinimumValueToBuy.String(), - "get_value": value.String(), - }), + Info: EncodeError(code.NewMinimumValueToBuyReached(data.MinimumValueToBuy.String(), value.String(), coinTo.GetFullSymbol(), coinTo.ID().String())), } } @@ -219,11 +201,11 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } - if errResp := CheckForCoinSupplyOverflow(coinTo.Volume(), value, coinTo.MaxSupply()); errResp != nil { + if errResp := CheckForCoinSupplyOverflow(coinTo, value); errResp != nil { return nil, nil, nil, errResp } @@ -243,7 +225,7 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota commission := big.NewInt(0).Set(commissionInBaseCoin) if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + coin := context.Coins().GetCoin(tx.GasCoin) if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { return nil, nil, nil, &Response{ @@ -253,11 +235,12 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota types.GetBaseCoin(), commissionInBaseCoin.String(), types.GetBaseCoin()), - Info: EncodeError(map[string]string{ - "has_value": coin.Reserve().String(), - "required_value": commissionInBaseCoin.String(), - "gas_coin": fmt.Sprintf("%s", types.GetBaseCoin()), - }), + Info: EncodeError(code.NewCoinReserveNotSufficient( + coin.GetFullSymbol(), + coin.ID().String(), + coin.Reserve().String(), + commissionInBaseCoin.String(), + )), } } @@ -266,7 +249,7 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -276,41 +259,41 @@ func (data SellCoinData) TotalSpend(tx *Transaction, context *state.State) (Tota return total, conversions, value, nil } -func (data SellCoinData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data SellCoinData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.ValueToSell == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } - if data.CoinToSell == data.CoinToBuy { + if !context.Coins().Exists(data.CoinToSell) { return &Response{ - Code: code.CrossConvert, - Log: fmt.Sprintf("\"From\" coin equals to \"to\" coin"), - Info: EncodeError(map[string]string{ - "coin_to_sell": fmt.Sprintf("%s", data.CoinToSell), - "coin_to_buy": fmt.Sprintf("%s", data.CoinToBuy), - }), + Code: code.CoinNotExists, + Log: "Coin not exists", + Info: EncodeError(code.NewCoinNotExists("", data.CoinToSell.String())), } } - if !context.Coins.Exists(data.CoinToSell) { + if !context.Coins().Exists(data.CoinToBuy) { return &Response{ Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists"), - Info: EncodeError(map[string]string{ - "coin_to_sell": fmt.Sprintf("%s", data.CoinToSell), - }), + Log: "Coin not exists", + Info: EncodeError(code.NewCoinNotExists("", data.CoinToBuy.String())), } } - if !context.Coins.Exists(data.CoinToBuy) { + if data.CoinToSell == data.CoinToBuy { return &Response{ - Code: code.CoinNotExists, - Log: fmt.Sprintf("Coin not exists"), - Info: EncodeError(map[string]string{ - "coin_to_buy": fmt.Sprintf("%s", data.CoinToBuy), - }), + Code: code.CrossConvert, + Log: "\"From\" coin equals to \"to\" coin", + Info: EncodeError(code.NewCrossConvert( + data.CoinToSell.String(), + context.Coins().GetCoin(data.CoinToSell).GetFullSymbol(), + data.CoinToBuy.String(), + context.Coins().GetCoin(data.CoinToBuy).GetFullSymbol()), + ), } } @@ -326,57 +309,60 @@ func (data SellCoinData) Gas() int64 { return commissions.ConvertTx } -func (data SellCoinData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data SellCoinData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } - response := data.BasicCheck(tx, context) + response := data.BasicCheck(tx, checkState) if response != nil { return *response } - totalSpends, conversions, value, response := data.TotalSpend(tx, context) + totalSpends, conversions, value, response := data.TotalSpend(tx, checkState) if response != nil { return *response } for _, ts := range totalSpends { - if context.Accounts.GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + if checkState.Accounts().GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + coin := checkState.Coins().GetCoin(ts.Coin) + return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s.", sender.String(), ts.Value.String(), - ts.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": ts.Value.String(), - "coin": fmt.Sprintf("%s", ts.Coin), - }), + coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), ts.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } } - errResp := checkConversionsReserveUnderflow(conversions, context) + errResp := checkConversionsReserveUnderflow(conversions, checkState) if errResp != nil { return *errResp } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { for _, ts := range totalSpends { - context.Accounts.SubBalance(sender, ts.Coin, ts.Value) + deliverState.Accounts.SubBalance(sender, ts.Coin, ts.Value) } for _, conversion := range conversions { - context.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) - context.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) + deliverState.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) + deliverState.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) - context.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) - context.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) + deliverState.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) + deliverState.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) } rewardPool.Add(rewardPool, tx.CommissionInBaseCoin()) - context.Accounts.AddBalance(sender, data.CoinToBuy, value) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.AddBalance(sender, data.CoinToBuy, value) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ diff --git a/core/transaction/sell_coin_test.go b/core/transaction/sell_coin_test.go index a227dcb7b..312ca9024 100644 --- a/core/transaction/sell_coin_test.go +++ b/core/transaction/sell_coin_test.go @@ -1,25 +1,26 @@ package transaction import ( + "math/big" + "sync" + "testing" + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/formula" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" ) func TestSellCoinTx(t *testing.T) { cState := getState() - createTestCoin(cState) + buyCoinID := createTestCoin(cState) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -27,7 +28,7 @@ func TestSellCoinTx(t *testing.T) { data := SellCoinData{ CoinToSell: coin, ValueToSell: helpers.BipToPip(big.NewInt(10)), - CoinToBuy: getTestCoinSymbol(), + CoinToBuy: buyCoinID, MinimumValueToBuy: minValToBuy, } @@ -57,7 +58,7 @@ func TestSellCoinTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error: %s", response.Log) @@ -70,30 +71,32 @@ func TestSellCoinTx(t *testing.T) { } targetTestBalance, _ := big.NewInt(0).SetString("999955002849793446", 10) - testBalance := cState.Accounts.GetBalance(addr, getTestCoinSymbol()) + testBalance := cState.Accounts.GetBalance(addr, buyCoinID) if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", getTestCoinSymbol(), targetTestBalance, testBalance) } + + checkState(t, cState) } func TestSellCoinTxBaseToCustomBaseCommission(t *testing.T) { // sell_coin: MNT // buy_coin: TEST // gas_coin: MNT + cState := getState() - coinToSell := types.GetBaseCoin() + coinToSell := types.GetBaseCoinID() coinToBuy := types.StrToCoinSymbol("TEST") - gasCoin := types.GetBaseCoin() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) toSell := helpers.BipToPip(big.NewInt(100)) - cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + coinToBuyID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() cState.Accounts.AddBalance(addr, coinToSell, initialBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSell, coinToBuyID, gasCoin, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -104,13 +107,13 @@ func TestSellCoinTxBaseToCustomBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell) if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) @@ -126,7 +129,7 @@ func TestSellCoinTxBaseToCustomBaseCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Add(estimatedReserve, toSell) @@ -139,6 +142,8 @@ func TestSellCoinTxBaseToCustomBaseCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { @@ -147,20 +152,22 @@ func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { // gas_coin: MNT coinToSell := types.StrToCoinSymbol("TEST") - coinToBuy := types.GetBaseCoin() - gasCoin := types.GetBaseCoin() + coinToBuy := types.GetBaseCoinID() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + coinToSellID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume.Add(initialVolume, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSellID, coinToBuy, gasCoin, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -171,7 +178,7 @@ func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -186,7 +193,7 @@ func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { @@ -194,7 +201,7 @@ func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume, initialReserve, crr, toSell)) @@ -207,6 +214,8 @@ func TestSellCoinTxCustomToBaseBaseCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { @@ -216,20 +225,22 @@ func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST2") - gasCoin := types.GetBaseCoin() + gasCoin := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + initialVolume1.Add(initialVolume1, initialBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSellID, coinToBuyID, gasCoin, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -240,20 +251,20 @@ func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { t.Fatalf("Buy coin balance is not correct. Expected %s, got %s", estimatedBuyBalance.String(), buyCoinBalance.String()) } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { @@ -262,7 +273,7 @@ func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -278,7 +289,7 @@ func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { } { - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve2) estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -292,6 +303,8 @@ func TestSellCoinTxCustomToCustomBaseCommission(t *testing.T) { t.Fatalf("Wrong coin supply") } } + + checkState(t, cState) } func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { @@ -299,21 +312,22 @@ func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { // buy_coin: TEST // gas_coin: TEST - coinToSell := types.GetBaseCoin() + coinToSell := types.GetBaseCoinID() coinToBuy := types.StrToCoinSymbol("TEST") - gasCoin := types.StrToCoinSymbol("TEST") initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) + coinToBuyID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() cState.Accounts.AddBalance(addr, coinToSell, initialBalance) - cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + cState.Accounts.AddBalance(addr, coinToBuyID, initialGasBalance) + cState.Coins.AddVolume(coinToBuyID, initialGasBalance) + initialVolume.Add(initialVolume, initialGasBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSell, coinToBuyID, coinToBuyID, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -324,13 +338,13 @@ func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins + commission - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) estimatedReturn := formula.CalculatePurchaseReturn(initialVolume, initialReserve, crr, toSell) estimatedBuyBalance := big.NewInt(0).Set(estimatedReturn) estimatedBuyBalance.Add(estimatedBuyBalance, initialGasBalance) @@ -348,7 +362,7 @@ func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Add(estimatedReserve, toSell) @@ -363,6 +377,8 @@ func TestSellCoinTxBaseToCustomCustomCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { @@ -371,18 +387,19 @@ func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { // gas_coin: TEST coinToSell := types.StrToCoinSymbol("TEST") - coinToBuy := types.GetBaseCoin() - gasCoin := types.StrToCoinSymbol("TEST") + coinToBuy := types.GetBaseCoinID() initialBalance := helpers.BipToPip(big.NewInt(10000000)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) + coinToSellID, initialVolume, initialReserve, crr := createTestCoinWithSymbol(cState, coinToSell) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume.Add(initialVolume, initialBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSellID, coinToBuy, coinToSellID, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -393,7 +410,7 @@ func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -407,7 +424,7 @@ func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { } // check sold coins + commission - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume, toSell), big.NewInt(0).Sub(initialReserve, estimatedReturn), crr, tx.CommissionInBaseCoin())) @@ -416,7 +433,7 @@ func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { } // check reserve and supply - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve) estimatedReserve.Sub(estimatedReserve, estimatedReturn) @@ -431,6 +448,8 @@ func TestSellCoinTxCustomToBaseCustomCommission(t *testing.T) { if coinData.Volume().Cmp(estimatedSupply) != 0 { t.Fatalf("Wrong coin supply") } + + checkState(t, cState) } func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { @@ -440,18 +459,19 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST2") - gasCoin := types.StrToCoinSymbol("TEST1") initialBalance := helpers.BipToPip(big.NewInt(10000000)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume1.Add(initialVolume1, initialBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSellID, coinToBuyID, coinToSellID, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -462,13 +482,13 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) bipReturn := formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell) estimatedBuyBalance := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, bipReturn) if buyCoinBalance.Cmp(estimatedBuyBalance) != 0 { @@ -476,7 +496,7 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) commission := formula.CalculateSaleAmount(big.NewInt(0).Sub(initialVolume1, toSell), big.NewInt(0).Sub(initialReserve1, bipReturn), crr1, tx.CommissionInBaseCoin()) @@ -487,7 +507,7 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -505,7 +525,7 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { } { - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve2) estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -519,6 +539,8 @@ func TestSellCoinTxCustomToCustomCustom1Commission(t *testing.T) { t.Fatalf("Wrong coin supply") } } + + checkState(t, cState) } func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { @@ -528,20 +550,24 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { coinToSell := types.StrToCoinSymbol("TEST1") coinToBuy := types.StrToCoinSymbol("TEST2") - gasCoin := types.StrToCoinSymbol("TEST2") initialBalance := helpers.BipToPip(big.NewInt(10000000)) initialGasBalance := helpers.BipToPip(big.NewInt(1)) toSell := helpers.BipToPip(big.NewInt(100)) cState := getState() - initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) - initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) + coinToSellID, initialVolume1, initialReserve1, crr1 := createTestCoinWithSymbol(cState, coinToSell) + coinToBuyID, initialVolume2, initialReserve2, crr2 := createTestCoinWithSymbol(cState, coinToBuy) privateKey, addr := getAccount() - cState.Accounts.AddBalance(addr, coinToSell, initialBalance) - cState.Accounts.AddBalance(addr, gasCoin, initialGasBalance) + cState.Accounts.AddBalance(addr, coinToSellID, initialBalance) + cState.Coins.AddVolume(coinToSellID, initialBalance) + initialVolume1.Add(initialVolume1, initialBalance) + + cState.Accounts.AddBalance(addr, coinToBuyID, initialGasBalance) + cState.Coins.AddVolume(coinToBuyID, initialGasBalance) + initialVolume2.Add(initialVolume2, initialGasBalance) - tx := createSellCoinTx(coinToSell, coinToBuy, gasCoin, toSell, 1) + tx := createSellCoinTx(coinToSellID, coinToBuyID, coinToBuyID, toSell, 1) if err := tx.Sign(privateKey); err != nil { t.Fatal(err) } @@ -552,13 +578,13 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { } // check response - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != code.OK { t.Fatalf("Response code is not 0. Error %s", response.Log) } // check received coins - buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuy) + buyCoinBalance := cState.Accounts.GetBalance(addr, coinToBuyID) bipReturn := formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell) estimatedReturn := formula.CalculatePurchaseReturn(initialVolume2, initialReserve2, crr2, bipReturn) commission := formula.CalculateSaleAmount(big.NewInt(0).Add(initialVolume2, estimatedReturn), big.NewInt(0).Add(initialReserve2, bipReturn), crr2, tx.CommissionInBaseCoin()) @@ -571,7 +597,7 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { } // check sold coins - sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSell) + sellCoinBalance := cState.Accounts.GetBalance(addr, coinToSellID) estimatedSellCoinBalance := big.NewInt(0).Set(initialBalance) estimatedSellCoinBalance.Sub(estimatedSellCoinBalance, toSell) if sellCoinBalance.Cmp(estimatedSellCoinBalance) != 0 { @@ -580,7 +606,7 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { // check reserve and supply { - coinData := cState.Coins.GetCoin(coinToSell) + coinData := cState.Coins.GetCoin(coinToSellID) estimatedReserve := big.NewInt(0).Set(initialReserve1) estimatedReserve.Sub(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -596,7 +622,7 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { } { - coinData := cState.Coins.GetCoin(coinToBuy) + coinData := cState.Coins.GetCoin(coinToBuyID) estimatedReserve := big.NewInt(0).Set(initialReserve2) estimatedReserve.Add(estimatedReserve, formula.CalculateSaleReturn(initialVolume1, initialReserve1, crr1, toSell)) @@ -612,9 +638,361 @@ func TestSellCoinTxCustomToCustomCustom2Commission(t *testing.T) { t.Fatalf("Wrong coin supply") } } + + checkState(t, cState) +} + +func TestSellCoinTxToCoinSupplyOverflow(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, sellCoinID, helpers.BipToPip(big.NewInt(100))) + + coinToBuy := cState.Coins.GetCoin(coinToBuyID) + coinToBuy.CMaxSupply = big.NewInt(1) + + tx := createSellCoinTx(sellCoinID, coinToBuyID, sellCoinID, helpers.BipToPip(big.NewInt(90)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinSupplyOverflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinSupplyOverflow, response.Log) + } + + checkState(t, cState) + + // custom buy and sell coins + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + tx = createSellCoinTx(coinToSellID, coinToBuyID, coinToSellID, helpers.BipToPip(big.NewInt(90)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinSupplyOverflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinSupplyOverflow, response.Log) + } + + checkState(t, cState) +} + +func TestSellCoinTxToMinimumValueToBuyReached(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, sellCoinID := createTestCoin(cState), types.GetBaseCoinID() + + valueToSell := big.NewInt(2e18) + cState.Accounts.AddBalance(addr, sellCoinID, helpers.BipToPip(big.NewInt(2))) + + data := SellCoinData{ + CoinToBuy: coinToBuyID, + ValueToSell: valueToSell, + CoinToSell: sellCoinID, + MinimumValueToBuy: big.NewInt(9e18), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx := &Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: sellCoinID, + Type: TypeSellCoin, + Data: encodedData, + SignatureType: SigTypeSingle, + decodedData: data, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) + + // coin to buy == base coin + + cState.Accounts.AddBalance(addr, coinToBuyID, helpers.BipToPip(big.NewInt(100000))) + + data.CoinToBuy = sellCoinID + data.CoinToSell = coinToBuyID + data.MinimumValueToBuy = big.NewInt(9e18) + data.ValueToSell = big.NewInt(1) + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) + + // custom buy and sell coins + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + coinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(coinToSellID.Uint32()) + + data.CoinToBuy = coinToBuyID + data.CoinToSell = coinToSellID + data.MinimumValueToBuy = big.NewInt(9e18) + data.ValueToSell = big.NewInt(1) + + encodedData, err = rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx.Data = encodedData + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.MinimumValueToBuyReached { + t.Fatalf("Response code is not %d. Error %s", code.MinimumValueToBuyReached, response.Log) + } + + checkState(t, cState) +} + +func TestSellCoinTxToCoinReserveNotSufficient(t *testing.T) { + cState := getState() + privateKey, addr := getAccount() + coinToBuyID, coinToSellID := createTestCoin(cState), types.GetBaseCoinID() + + cState.Coins.Create( + cState.App.GetNextCoinID(), + types.StrToCoinSymbol("TEST9"), + "TEST COIN", + helpers.BipToPip(big.NewInt(100000)), + 10, + helpers.BipToPip(big.NewInt(100000)), + helpers.BipToPip(big.NewInt(1000000)), + nil, + ) + + customCoinToSellID := cState.App.GetNextCoinID() + cState.App.SetCoinsCount(customCoinToSellID.Uint32()) + + cState.Accounts.AddBalance(types.Address{0}, customCoinToSellID, helpers.BipToPip(big.NewInt(100000))) + cState.Accounts.AddBalance(addr, coinToSellID, helpers.BipToPip(big.NewInt(5000000))) + + tx := createSellCoinTx(coinToBuyID, coinToSellID, coinToBuyID, helpers.BipToPip(big.NewInt(100000)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) + + // gas coin == coin to sell + + cState.Coins.SubReserve(coinToBuyID, helpers.BipToPip(big.NewInt(100000))) + + tx = createSellCoinTx(coinToBuyID, customCoinToSellID, coinToBuyID, helpers.BipToPip(big.NewInt(1)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveNotSufficient { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveNotSufficient, response.Log) + } + + checkState(t, cState) +} + +func TestSellCoinTxInsufficientFunds(t *testing.T) { + cState := getState() + + coinToBuyID, coinToSellID := createTestCoin(cState), types.GetBaseCoinID() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + tx := createSellCoinTx(coinToSellID, coinToBuyID, coinToSellID, helpers.BipToPip(big.NewInt(1000)), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestSellCoinTxEqualCoins(t *testing.T) { + cState := getState() + coinID := createTestCoin(cState) + privateKey, _ := crypto.GenerateKey() + + tx := createSellCoinTx(coinID, coinID, coinID, big.NewInt(1), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CrossConvert { + t.Fatalf("Response code is not %d. Error %s", code.CrossConvert, response.Log) + } + + checkState(t, cState) +} + +func TestSellCoinTxToNonExistCoins(t *testing.T) { + cState := getState() + coinID := createTestCoin(cState) + privateKey, _ := crypto.GenerateKey() + + tx := createSellCoinTx(5, coinID, coinID, big.NewInt(1), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + tx = createSellCoinTx(coinID, 5, coinID, big.NewInt(1), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) + + tx = createSellCoinTx(coinID, types.GetBaseCoinID(), 5, big.NewInt(1), 1) + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) } -func createSellCoinTx(sellCoin, buyCoin, gasCoin types.CoinSymbol, valueToSell *big.Int, nonce uint64) *Transaction { +func createSellCoinTx(sellCoin, buyCoin, gasCoin types.CoinID, valueToSell *big.Int, nonce uint64) *Transaction { data := SellCoinData{ CoinToSell: sellCoin, ValueToSell: valueToSell, diff --git a/core/transaction/send.go b/core/transaction/send.go index 3fb7c0842..9d4483120 100644 --- a/core/transaction/send.go +++ b/core/transaction/send.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -14,24 +13,17 @@ import ( ) type SendData struct { - Coin types.CoinSymbol + Coin types.CoinID To types.Address Value *big.Int } -func (data SendData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Coin string `json:"coin"` - To string `json:"to"` - Value string `json:"value"` - }{ - Coin: data.Coin.String(), - To: data.To.String(), - Value: data.Value.String(), - }) +type Coin struct { + ID uint32 `json:"id"` + Symbol string `json:"symbol"` } -func (data SendData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { +func (data SendData) TotalSpend(tx *Transaction, context *state.CheckState) (TotalSpends, []Conversion, *big.Int, *Response) { total := TotalSpends{} var conversions []Conversion @@ -39,32 +31,19 @@ func (data SendData) TotalSpend(tx *Transaction, context *state.State) (TotalSpe commission := big.NewInt(0).Set(commissionInBaseCoin) if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + coin := context.Coins().GetCoin(tx.GasCoin) errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) if errResp != nil { return nil, nil, nil, errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return nil, nil, nil, &Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", - coin.Reserve().String(), - commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has": coin.Reserve().String(), - "required": commissionInBaseCoin.String(), - }), - } - } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) conversions = append(conversions, Conversion{ FromCoin: tx.GasCoin, FromAmount: commission, FromReserve: commissionInBaseCoin, - ToCoin: types.GetBaseCoin(), + ToCoin: types.GetBaseCoinID(), }) } @@ -74,20 +53,20 @@ func (data SendData) TotalSpend(tx *Transaction, context *state.State) (TotalSpe return total, conversions, nil, nil } -func (data SendData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data SendData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.Value == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } - if !context.Coins.Exists(data.Coin) { + if !context.Coins().Exists(data.Coin) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", data.Coin), - Info: EncodeError(map[string]string{ - "coin": fmt.Sprintf("%s", data.Coin), - }), + Info: EncodeError(code.NewCoinNotExists("", data.Coin.String())), } } @@ -103,59 +82,63 @@ func (data SendData) Gas() int64 { return commissions.SendTx } -func (data SendData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data SendData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } - totalSpends, conversions, _, response := data.TotalSpend(tx, context) + totalSpends, conversions, _, response := data.TotalSpend(tx, checkState) if response != nil { return *response } for _, ts := range totalSpends { - if context.Accounts.GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + if checkState.Accounts().GetBalance(sender, ts.Coin).Cmp(ts.Value) < 0 { + coin := checkState.Coins().GetCoin(ts.Coin) + return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s.", sender.String(), ts.Value.String(), - ts.Coin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": ts.Value.String(), - "coin": fmt.Sprintf("%s", ts.Coin), - }), + coin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), ts.Value.String(), coin.GetFullSymbol(), coin.ID().String())), } } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { for _, ts := range totalSpends { - context.Accounts.SubBalance(sender, ts.Coin, ts.Value) + deliverState.Accounts.SubBalance(sender, ts.Coin, ts.Value) } for _, conversion := range conversions { - context.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) - context.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) + deliverState.Coins.SubVolume(conversion.FromCoin, conversion.FromAmount) + deliverState.Coins.SubReserve(conversion.FromCoin, conversion.FromReserve) - context.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) - context.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) + deliverState.Coins.AddVolume(conversion.ToCoin, conversion.ToAmount) + deliverState.Coins.AddReserve(conversion.ToCoin, conversion.ToReserve) } rewardPool.Add(rewardPool, tx.CommissionInBaseCoin()) - context.Accounts.AddBalance(data.To, data.Coin, data.Value) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.AddBalance(data.To, data.Coin, data.Value) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeSend)}))}, kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, kv.Pair{Key: []byte("tx.to"), Value: []byte(hex.EncodeToString(data.To[:]))}, - kv.Pair{Key: []byte("tx.coin"), Value: []byte(data.Coin.String())}, + kv.Pair{Key: []byte("tx.coin_id"), Value: []byte(data.Coin.String())}, } return Response{ diff --git a/core/transaction/send_test.go b/core/transaction/send_test.go index 97378715f..88fe8af1c 100644 --- a/core/transaction/send_test.go +++ b/core/transaction/send_test.go @@ -1,14 +1,16 @@ package transaction import ( + "math/big" + "sync" + "testing" + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state/accounts" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "sync" - "testing" ) func TestSendTx(t *testing.T) { @@ -16,7 +18,7 @@ func TestSendTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -53,7 +55,7 @@ func TestSendTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error: %s", response.Log) } @@ -69,6 +71,8 @@ func TestSendTx(t *testing.T) { if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", to.String(), targetTestBalance, testBalance) } + + checkState(t, cState) } func TestSendMultisigTx(t *testing.T) { @@ -80,9 +84,9 @@ func TestSendMultisigTx(t *testing.T) { privateKey2, _ := crypto.GenerateKey() addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msig := cState.Accounts.CreateMultisig([]uint{1, 1}, []types.Address{addr1, addr2}, 1, 1) + msig := cState.Accounts.CreateMultisig([]uint32{1, 1}, []types.Address{addr1, addr2}, 1, accounts.CreateMultisigAddress(addr1, 1)) cState.Accounts.AddBalance(msig, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -121,7 +125,7 @@ func TestSendMultisigTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error: %s", response.Log) } @@ -137,6 +141,8 @@ func TestSendMultisigTx(t *testing.T) { if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", to.String(), targetTestBalance, testBalance) } + + checkState(t, cState) } func TestSendFailedMultisigTx(t *testing.T) { @@ -148,9 +154,9 @@ func TestSendFailedMultisigTx(t *testing.T) { privateKey2, _ := crypto.GenerateKey() addr2 := crypto.PubkeyToAddress(privateKey2.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() - msig := cState.Accounts.CreateMultisig([]uint{1, 3}, []types.Address{addr1, addr2}, 3, 1) + msig := cState.Accounts.CreateMultisig([]uint32{1, 3}, []types.Address{addr1, addr2}, 3, accounts.CreateMultisigAddress(addr1, 1)) cState.Accounts.AddBalance(msig, coin, helpers.BipToPip(big.NewInt(1000000))) @@ -189,9 +195,9 @@ func TestSendFailedMultisigTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) - if response.Code != code.IncorrectMultiSignature { - t.Fatalf("Response code is not %d. Gor: %d", code.IncorrectMultiSignature, response.Code) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.NotEnoughMultisigVotes { + t.Fatalf("Response code is not %d. Gor: %d", code.NotEnoughMultisigVotes, response.Code) } targetBalance, _ := big.NewInt(0).SetString("1000000000000000000000000", 10) @@ -205,4 +211,168 @@ func TestSendFailedMultisigTx(t *testing.T) { if testBalance.Cmp(targetTestBalance) != 0 { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", to.String(), targetTestBalance, testBalance) } + + checkState(t, cState) +} + +func TestSendWithNotExistedCoin(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + coin := types.CoinID(5) + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := SendData{ + Coin: coin, + To: to, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: types.GetBaseCoinID(), + Type: TypeSend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestSendTxWithCustomCoin(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := SendData{ + Coin: coin, + To: to, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error: %s", response.Log) + } + + targetBalance, _ := big.NewInt(0).SetString("999989988999999504999969", 10) + balance := cState.Accounts.GetBalance(addr, coin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", addr.String(), targetBalance, balance) + } + + targetTestBalance, _ := big.NewInt(0).SetString("10000000000000000000", 10) + testBalance := cState.Accounts.GetBalance(to, coin) + if testBalance.Cmp(targetTestBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", to.String(), targetTestBalance, testBalance) + } + + checkState(t, cState) +} + +func TestSendTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + value := helpers.BipToPip(big.NewInt(10)) + to := types.Address([20]byte{1}) + + data := SendData{ + Coin: coin, + To: to, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSend, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error: %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/set_halt_block.go b/core/transaction/set_halt_block.go new file mode 100644 index 000000000..288a1da1b --- /dev/null +++ b/core/transaction/set_halt_block.go @@ -0,0 +1,124 @@ +package transaction + +import ( + "encoding/hex" + "fmt" + "math/big" + "strconv" + + "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" + "github.com/tendermint/tendermint/libs/kv" +) + +type SetHaltBlockData struct { + PubKey types.Pubkey + Height uint64 +} + +func (data SetHaltBlockData) GetPubKey() types.Pubkey { + return data.PubKey +} + +func (data SetHaltBlockData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + if !context.Candidates().Exists(data.PubKey) { + return &Response{ + Code: code.CandidateNotFound, + Log: "Candidate with such public key not found", + Info: EncodeError(code.NewCandidateNotFound(data.PubKey.String())), + } + } + + if context.Halts().IsHaltExists(data.Height, data.PubKey) { + return &Response{ + Code: code.HaltAlreadyExists, + Log: "Halt with such public key and height already exists", + Info: EncodeError(code.NewWrongHaltHeight(strconv.FormatUint(data.Height, 10), data.GetPubKey().String())), + } + } + + return checkCandidateOwnership(data, tx, context) +} + +func (data SetHaltBlockData) String() string { + return fmt.Sprintf("SET HALT BLOCK pubkey:%s height:%d", + hexutil.Encode(data.PubKey[:]), data.Height) +} + +func (data SetHaltBlockData) Gas() int64 { + return commissions.SetHaltBlock +} + +func (data SetHaltBlockData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { + sender, _ := tx.Sender() + + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) + if response != nil { + return *response + } + + if data.Height < currentBlock { + return Response{ + Code: code.WrongHaltHeight, + Log: fmt.Sprintf("Halt height should be equal or bigger than current: %d", currentBlock), + Info: EncodeError(code.NewWrongHaltHeight(strconv.FormatUint(data.Height, 10), data.GetPubKey().String())), + } + } + + commissionInBaseCoin := tx.CommissionInBaseCoin() + commission := big.NewInt(0).Set(commissionInBaseCoin) + + if !tx.GasCoin.IsBaseCoin() { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp + } + + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) + } + + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) + + return Response{ + Code: code.InsufficientFunds, + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), + } + } + + if deliverState, ok := context.(*state.State); ok { + rewardPool.Add(rewardPool, commissionInBaseCoin) + + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Halts.AddHaltBlock(data.Height, data.PubKey) + deliverState.Accounts.SetNonce(sender, tx.Nonce) + } + + tags := kv.Pairs{ + kv.Pair{Key: []byte("tx.type"), Value: []byte(hex.EncodeToString([]byte{byte(TypeSetHaltBlock)}))}, + kv.Pair{Key: []byte("tx.from"), Value: []byte(hex.EncodeToString(sender[:]))}, + } + + return Response{ + Code: code.OK, + GasUsed: tx.Gas(), + GasWanted: tx.Gas(), + Tags: tags, + } +} diff --git a/core/transaction/set_halt_block_test.go b/core/transaction/set_halt_block_test.go new file mode 100644 index 000000000..a4bec088e --- /dev/null +++ b/core/transaction/set_halt_block_test.go @@ -0,0 +1,446 @@ +package transaction + +import ( + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" + + "math/big" + "math/rand" + "sync" + "testing" + + db "github.com/tendermint/tm-db" +) + +func TestSetHaltBlockTx(t *testing.T) { + cState, err := state.NewState(500000, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + haltHeight := 500000 + uint64(100) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + targetBalance, _ := big.NewInt(0).SetString("0", 10) + balance := cState.Accounts.GetBalance(addr, coin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) + } + + halts := cState.Halts.GetHaltBlocks(haltHeight) + if halts == nil { + t.Fatalf("No halts on the height: %d", haltHeight) + } + + haltBlocks := halts.List + if len(haltBlocks) != 1 { + t.Fatalf("Halt blocks are not correct. Expected halts size: %d, got %d", 1, len(haltBlocks)) + } + + haltBlock := haltBlocks[0] + if haltBlock.Pubkey != pubkey { + t.Fatalf("Wront halt block pubkey. Expected pubkey: %s, got %s", pubkey, haltBlock.Pubkey.String()+"asd") + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxWithWrongHeight(t *testing.T) { + currentHeight := uint64(500000 + 5) + cState, err := state.NewState(currentHeight, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + haltHeight := currentHeight - 1 + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), currentHeight, &sync.Map{}, 0) + if response.Code != code.WrongHaltHeight { + t.Fatalf("Response code is not %d", code.WrongHaltHeight) + } + + halts := cState.Halts.GetHaltBlocks(haltHeight) + if halts != nil { + t.Fatalf("Halts found at height: %d", haltHeight) + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxWithWrongOwnership(t *testing.T) { + currentHeight := uint64(500000 + 5) + cState, err := state.NewState(currentHeight, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + haltHeight := currentHeight + 1 + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + privateKey2, _ := crypto.GenerateKey() + if err := tx.Sign(privateKey2); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), currentHeight, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCandidate { + t.Fatalf("Response code is not %d", code.IsNotOwnerOfCandidate) + } + + halts := cState.Halts.GetHaltBlocks(haltHeight) + if halts != nil { + t.Fatalf("Halts found at height: %d", haltHeight) + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxToNonExistCandidate(t *testing.T) { + cState, err := state.NewState(500000, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + createDefaultValidator(cState) + + haltHeight := 500000 + uint64(100) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxToInsufficientFunds(t *testing.T) { + cState, err := state.NewState(500000, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + haltHeight := 500000 + uint64(100) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxToGasCoinReserveUnderflow(t *testing.T) { + cState, err := state.NewState(500000, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + createDefaultValidator(cState) + + haltHeight := 500000 + uint64(100) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Commit() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestSetHaltBlockTxToAlreadyExistenHalt(t *testing.T) { + cState, err := state.NewState(500000, db.NewMemDB(), nil, 1, 1) + if err != nil { + t.Fatalf("Cannot load state. Error %s", err) + } + + haltHeight := 500000 + uint64(100) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := [32]byte{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Validators.Create(pubkey, helpers.BipToPip(big.NewInt(1))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1))) + cState.Halts.AddHaltBlock(haltHeight, pubkey) + + data := SetHaltBlockData{ + PubKey: pubkey, + Height: haltHeight, + } + + encodedData, err := rlp.EncodeToBytes(data) + + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetHaltBlock, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 500000, &sync.Map{}, 0) + if response.Code != code.HaltAlreadyExists { + t.Fatalf("response code is not %d. Error %s", code.HaltAlreadyExists, response.Log) + } + + checkState(t, cState) +} diff --git a/core/transaction/switch_candidate_status.go b/core/transaction/switch_candidate_status.go index bd70c9bf3..54995e41f 100644 --- a/core/transaction/switch_candidate_status.go +++ b/core/transaction/switch_candidate_status.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -17,24 +16,12 @@ type SetCandidateOnData struct { PubKey types.Pubkey } -func (data SetCandidateOnData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - PubKey string `json:"pub_key"` - }{ - PubKey: data.PubKey.String(), - }) -} - func (data SetCandidateOnData) GetPubKey() types.Pubkey { return data.PubKey } -func (data SetCandidateOnData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data SetCandidateOnData) BasicCheck(tx *Transaction, context *state.State) *Response { - return checkCandidateOwnership(data, tx, context) +func (data SetCandidateOnData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + return checkCandidateControl(data, tx, context) } func (data SetCandidateOnData) String() string { @@ -46,10 +33,16 @@ func (data SetCandidateOnData) Gas() int64 { return commissions.ToggleCandidateStatus } -func (data SetCandidateOnData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data SetCandidateOnData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -57,50 +50,34 @@ func (data SetCandidateOnData) Run(tx *Transaction, context *state.State, isChec commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Candidates.SetOnline(data.PubKey) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Candidates.SetOnline(data.PubKey) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ @@ -120,24 +97,12 @@ type SetCandidateOffData struct { PubKey types.Pubkey `json:"pub_key"` } -func (data SetCandidateOffData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - PubKey string `json:"pub_key"` - }{ - PubKey: data.PubKey.String(), - }) -} - func (data SetCandidateOffData) GetPubKey() types.Pubkey { return data.PubKey } -func (data SetCandidateOffData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data SetCandidateOffData) BasicCheck(tx *Transaction, context *state.State) *Response { - return checkCandidateOwnership(data, tx, context) +func (data SetCandidateOffData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { + return checkCandidateControl(data, tx, context) } func (data SetCandidateOffData) String() string { @@ -149,10 +114,16 @@ func (data SetCandidateOffData) Gas() int64 { return commissions.ToggleCandidateStatus } -func (data SetCandidateOffData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data SetCandidateOffData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -160,46 +131,34 @@ func (data SetCandidateOffData) Run(tx *Transaction, context *state.State, isChe commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) - - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) + if errResp != nil { + return *errResp } - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Candidates.SetOffline(data.PubKey) - context.Validators.SetToDrop(data.PubKey) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + deliverState.Candidates.SetOffline(data.PubKey) + deliverState.Validators.SetToDrop(data.PubKey) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ @@ -214,3 +173,28 @@ func (data SetCandidateOffData) Run(tx *Transaction, context *state.State, isChe Tags: tags, } } + +func checkCandidateControl(data CandidateTx, tx *Transaction, context *state.CheckState) *Response { + if !context.Candidates().Exists(data.GetPubKey()) { + return &Response{ + Code: code.CandidateNotFound, + Log: fmt.Sprintf("Candidate with such public key (%s) not found", data.GetPubKey().String()), + Info: EncodeError(code.NewCandidateNotFound(data.GetPubKey().String())), + } + } + + owner := context.Candidates().GetCandidateOwner(data.GetPubKey()) + control := context.Candidates().GetCandidateControl(data.GetPubKey()) + sender, _ := tx.Sender() + switch sender { + case owner, control: + default: + return &Response{ + Code: code.IsNotOwnerOfCandidate, + Log: "Sender is not an owner of a candidate", + Info: EncodeError(code.NewIsNotOwnerOfCandidate(sender.String(), data.GetPubKey().String(), owner.String(), control.String())), + } + } + + return nil +} diff --git a/core/transaction/switch_candidate_status_test.go b/core/transaction/switch_candidate_status_test.go index cab55ca3f..e43622dfa 100644 --- a/core/transaction/switch_candidate_status_test.go +++ b/core/transaction/switch_candidate_status_test.go @@ -1,15 +1,17 @@ package transaction import ( + "math/big" + "math/rand" + "sync" + "testing" + + "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "math/big" - "math/rand" - "sync" - "testing" ) func TestSwitchCandidateStatusTx(t *testing.T) { @@ -17,20 +19,19 @@ func TestSwitchCandidateStatusTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) pubkey := types.Pubkey{} rand.Read(pubkey[:]) - cState.Candidates.Create(addr, addr, pubkey, 10) + cState.Candidates.Create(addr, addr, addr, pubkey, 10) data := SetCandidateOnData{ PubKey: pubkey, } encodedData, err := rlp.EncodeToBytes(data) - if err != nil { t.Fatal(err) } @@ -50,13 +51,76 @@ func TestSwitchCandidateStatusTx(t *testing.T) { } encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + targetBalance, _ := big.NewInt(0).SetString("999999900000000000000000", 10) + balance := cState.Accounts.GetBalance(addr, coin) + if balance.Cmp(targetBalance) != 0 { + t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) + } + + candidate := cState.Candidates.GetCandidate(pubkey) + + if candidate == nil { + t.Fatalf("Candidate not found") + } + + if candidate.Status != candidates.CandidateStatusOnline { + t.Fatalf("Status has not changed") + } + + checkState(t, cState) +} + +func TestSetCandidateOffTx(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + data := SetCandidateOffData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) if err != nil { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOffline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } @@ -73,7 +137,275 @@ func TestSwitchCandidateStatusTx(t *testing.T) { t.Fatalf("Candidate not found") } - if candidate.Status != candidates.CandidateStatusOnline { + if candidate.Status != candidates.CandidateStatusOffline { t.Fatalf("Status has not changed") } + + checkState(t, cState) +} + +func TestSwitchCandidateStatusTxToNonExistCandidate(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + + data := SetCandidateOnData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOnline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestSwitchCandidateStatusTxToCandidateOwnership(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + addr2 := types.Address{0} + coin := types.GetBaseCoinID() + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr2, addr2, addr2, pubkey, 10) + + data := SetCandidateOnData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOnline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.IsNotOwnerOfCandidate { + t.Fatalf("Response code is not %d. Error %s", code.IsNotOwnerOfCandidate, response.Log) + } + + checkState(t, cState) +} + +func TestSwitchCandidateStatusToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + + coin := createTestCoin(cState) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(90000))) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + + data := SetCandidateOnData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOnline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) +} + +func TestSwitchCandidateStatusToInsufficientFundsForGas(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + + data := SetCandidateOnData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOnline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) + + cState.Candidates.SetOnline(pubkey) + + tx.Type = TypeSetCandidateOffline + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err = rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response = RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestSwitchCandidateStatusToCoinReserveUnderflow(t *testing.T) { + cState := getState() + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := createTestCoin(cState) + + pubkey := types.Pubkey{} + rand.Read(pubkey[:]) + cState.Candidates.Create(addr, addr, addr, pubkey, 10) + cState.Candidates.SetOnline(pubkey) + cState.Coins.AddVolume(coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Coins.SubReserve(coin, helpers.BipToPip(big.NewInt(100000))) + + data := SetCandidateOffData{ + PubKey: pubkey, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeSetCandidateOffline, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/transaction/transaction.go b/core/transaction/transaction.go index a91f25a59..d99d7f3bd 100644 --- a/core/transaction/transaction.go +++ b/core/transaction/transaction.go @@ -10,29 +10,37 @@ import ( "github.com/MinterTeam/minter-go-node/core/state/coins" "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/rlp" + "golang.org/x/crypto/sha3" "math/big" ) +// TxType of transaction is determined by a single byte. type TxType byte + type SigType byte const ( - TypeSend TxType = 0x01 - TypeSellCoin TxType = 0x02 - TypeSellAllCoin TxType = 0x03 - TypeBuyCoin TxType = 0x04 - TypeCreateCoin TxType = 0x05 - TypeDeclareCandidacy TxType = 0x06 - TypeDelegate TxType = 0x07 - TypeUnbond TxType = 0x08 - TypeRedeemCheck TxType = 0x09 - TypeSetCandidateOnline TxType = 0x0A - TypeSetCandidateOffline TxType = 0x0B - TypeCreateMultisig TxType = 0x0C - TypeMultisend TxType = 0x0D - TypeEditCandidate TxType = 0x0E + TypeSend TxType = 0x01 + TypeSellCoin TxType = 0x02 + TypeSellAllCoin TxType = 0x03 + TypeBuyCoin TxType = 0x04 + TypeCreateCoin TxType = 0x05 + TypeDeclareCandidacy TxType = 0x06 + TypeDelegate TxType = 0x07 + TypeUnbond TxType = 0x08 + TypeRedeemCheck TxType = 0x09 + TypeSetCandidateOnline TxType = 0x0A + TypeSetCandidateOffline TxType = 0x0B + TypeCreateMultisig TxType = 0x0C + TypeMultisend TxType = 0x0D + TypeEditCandidate TxType = 0x0E + TypeSetHaltBlock TxType = 0x0F + TypeRecreateCoin TxType = 0x10 + TypeEditCoinOwner TxType = 0x11 + TypeEditMultisig TxType = 0x12 + TypePriceVote TxType = 0x13 + TypeEditCandidatePublicKey TxType = 0x14 SigTypeSingle SigType = 0x01 SigTypeMulti SigType = 0x02 @@ -42,11 +50,15 @@ var ( ErrInvalidSig = errors.New("invalid transaction v, r, s values") ) +var ( + CommissionMultiplier = big.NewInt(10e14) +) + type Transaction struct { Nonce uint64 ChainID types.ChainID GasPrice uint32 - GasCoin types.CoinSymbol + GasCoin types.CoinID Type TxType Data RawData Payload []byte @@ -75,7 +87,7 @@ type RawData []byte type TotalSpends []TotalSpend -func (tss *TotalSpends) Add(coin types.CoinSymbol, value *big.Int) { +func (tss *TotalSpends) Add(coin types.CoinID, value *big.Int) { for i, t := range *tss { if t.Coin == coin { (*tss)[i].Value.Add((*tss)[i].Value, big.NewInt(0).Set(value)) @@ -90,15 +102,15 @@ func (tss *TotalSpends) Add(coin types.CoinSymbol, value *big.Int) { } type TotalSpend struct { - Coin types.CoinSymbol + Coin types.CoinID Value *big.Int } type Conversion struct { - FromCoin types.CoinSymbol + FromCoin types.CoinID FromAmount *big.Int FromReserve *big.Int - ToCoin types.CoinSymbol + ToCoin types.CoinID ToAmount *big.Int ToReserve *big.Int } @@ -106,9 +118,7 @@ type Conversion struct { type Data interface { String() string Gas() int64 - TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) - BasicCheck(tx *Transaction, context *state.State) *Response - Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response + Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response } func (tx *Transaction) Serialize() ([]byte, error) { @@ -283,7 +293,7 @@ func RecoverPlain(sighash types.Hash, R, S, Vb *big.Int) (types.Address, error) } func rlpHash(x interface{}) (h types.Hash) { - hw := sha3.NewKeccak256() + hw := sha3.NewLegacyKeccak256() err := rlp.Encode(hw, x) if err != nil { panic(err) @@ -292,38 +302,30 @@ func rlpHash(x interface{}) (h types.Hash) { return h } -func CheckForCoinSupplyOverflow(current *big.Int, delta *big.Int, max *big.Int) *Response { - total := big.NewInt(0).Set(current) +func CheckForCoinSupplyOverflow(coin *coins.Model, delta *big.Int) *Response { + total := big.NewInt(0).Set(coin.Volume()) total.Add(total, delta) - if total.Cmp(max) != -1 { + if total.Cmp(coin.MaxSupply()) != -1 { return &Response{ Code: code.CoinSupplyOverflow, Log: "coin supply overflow", - Info: EncodeError(map[string]string{ - "current": total.String(), - "delta": delta.String(), - "max": max.String(), - }), + Info: EncodeError(code.NewCoinSupplyOverflow(delta.String(), coin.Volume().String(), total.String(), coin.MaxSupply().String(), coin.GetFullSymbol(), coin.ID().String())), } } return nil } -func CheckReserveUnderflow(m *coins.Model, delta *big.Int) *Response { - total := big.NewInt(0).Sub(m.Reserve(), delta) +func CheckReserveUnderflow(coin *coins.Model, delta *big.Int) *Response { + total := big.NewInt(0).Sub(coin.Reserve(), delta) if total.Cmp(minCoinReserve) == -1 { min := big.NewInt(0).Add(minCoinReserve, delta) return &Response{ Code: code.CoinReserveUnderflow, - Log: fmt.Sprintf("coin %s reserve is too small (%s, required at least %s)", m.Symbol().String(), m.Reserve().String(), min.String()), - Info: EncodeError(map[string]string{ - "coin": m.Symbol().String(), - "coin_reserve": m.Reserve().String(), - "min_coin_reserve": min.String(), - }), + Log: fmt.Sprintf("coin %s reserve is too small (%s, required at least %s)", coin.GetFullSymbol(), coin.Reserve().String(), min.String()), + Info: EncodeError(code.NewCoinReserveUnderflow(delta.String(), coin.Reserve().String(), total.String(), minCoinReserve.String(), coin.GetFullSymbol(), coin.ID().String())), } } diff --git a/core/transaction/unbond.go b/core/transaction/unbond.go index 322d9228d..5cfd062cd 100644 --- a/core/transaction/unbond.go +++ b/core/transaction/unbond.go @@ -2,7 +2,6 @@ package transaction import ( "encoding/hex" - "encoding/json" "fmt" "github.com/MinterTeam/minter-go-node/core/code" "github.com/MinterTeam/minter-go-node/core/commissions" @@ -18,69 +17,65 @@ const unbondPeriod = 518400 type UnbondData struct { PubKey types.Pubkey - Coin types.CoinSymbol + Coin types.CoinID Value *big.Int } -func (data UnbondData) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - PubKey string `json:"pub_key"` - Coin string `json:"coin"` - Value string `json:"value"` - }{ - PubKey: data.PubKey.String(), - Coin: data.Coin.String(), - Value: data.Value.String(), - }) -} - -func (data UnbondData) TotalSpend(tx *Transaction, context *state.State) (TotalSpends, []Conversion, *big.Int, *Response) { - panic("implement me") -} - -func (data UnbondData) BasicCheck(tx *Transaction, context *state.State) *Response { +func (data UnbondData) BasicCheck(tx *Transaction, context *state.CheckState) *Response { if data.Value == nil { return &Response{ Code: code.DecodeError, - Log: "Incorrect tx data"} + Log: "Incorrect tx data", + Info: EncodeError(code.NewDecodeError()), + } } - if !context.Coins.Exists(data.Coin) { + if !context.Coins().Exists(data.Coin) { return &Response{ Code: code.CoinNotExists, Log: fmt.Sprintf("Coin %s not exists", data.Coin), - Info: EncodeError(map[string]string{ - "coin": fmt.Sprintf("%s", data.Coin), - }), + Info: EncodeError(code.NewCoinNotExists("", data.Coin.String())), } } - if !context.Candidates.Exists(data.PubKey) { + if !context.Candidates().Exists(data.PubKey) { return &Response{ Code: code.CandidateNotFound, - Log: fmt.Sprintf("Candidate with such public key not found"), - Info: EncodeError(map[string]string{ - "pub_key": data.PubKey.String(), - }), + Log: "Candidate with such public key not found", + Info: EncodeError(code.NewCandidateNotFound(data.PubKey.String())), } } sender, _ := tx.Sender() - stake := context.Candidates.GetStakeValueOfAddress(data.PubKey, sender, data.Coin) + + if waitlist := context.WaitList().Get(sender, data.PubKey, data.Coin); waitlist != nil { + value := big.NewInt(0).Sub(data.Value, waitlist.Value) + if value.Sign() < 1 { + return nil + } + return &Response{ + Code: code.InsufficientWaitList, + Log: "Insufficient amount at waitlist for sender account", + Info: EncodeError(code.NewInsufficientWaitList(waitlist.Value.String(), data.Value.String())), + } + } + + stake := context.Candidates().GetStakeValueOfAddress(data.PubKey, sender, data.Coin) if stake == nil { return &Response{ Code: code.StakeNotFound, - Log: fmt.Sprintf("Stake of current user not found")} + Log: "Stake of current user not found", + Info: EncodeError(code.NewStakeNotFound(data.PubKey.String(), sender.String(), data.Coin.String(), context.Coins().GetCoin(data.Coin).GetFullSymbol())), + } } if stake.Cmp(data.Value) < 0 { return &Response{ Code: code.InsufficientStake, - Log: fmt.Sprintf("Insufficient stake for sender account"), - Info: EncodeError(map[string]string{ - "pub_key": data.PubKey.String(), - })} + Log: "Insufficient stake for sender account", + Info: EncodeError(code.NewInsufficientStake(data.PubKey.String(), sender.String(), data.Coin.String(), context.Coins().GetCoin(data.Coin).GetFullSymbol(), stake.String(), data.Value.String())), + } } return nil @@ -95,10 +90,16 @@ func (data UnbondData) Gas() int64 { return commissions.UnbondTx } -func (data UnbondData) Run(tx *Transaction, context *state.State, isCheck bool, rewardPool *big.Int, currentBlock uint64) Response { +func (data UnbondData) Run(tx *Transaction, context state.Interface, rewardPool *big.Int, currentBlock uint64) Response { sender, _ := tx.Sender() - response := data.BasicCheck(tx, context) + var checkState *state.CheckState + var isCheck bool + if checkState, isCheck = context.(*state.CheckState); !isCheck { + checkState = state.NewCheckState(context.(*state.State)) + } + + response := data.BasicCheck(tx, checkState) if response != nil { return *response } @@ -106,54 +107,48 @@ func (data UnbondData) Run(tx *Transaction, context *state.State, isCheck bool, commissionInBaseCoin := tx.CommissionInBaseCoin() commission := big.NewInt(0).Set(commissionInBaseCoin) - if !tx.GasCoin.IsBaseCoin() { - coin := context.Coins.GetCoin(tx.GasCoin) + gasCoin := checkState.Coins().GetCoin(tx.GasCoin) - errResp := CheckReserveUnderflow(coin, commissionInBaseCoin) + if !tx.GasCoin.IsBaseCoin() { + errResp := CheckReserveUnderflow(gasCoin, commissionInBaseCoin) if errResp != nil { return *errResp } - if coin.Reserve().Cmp(commissionInBaseCoin) < 0 { - return Response{ - Code: code.CoinReserveNotSufficient, - Log: fmt.Sprintf("Coin reserve balance is not sufficient for transaction. Has: %s, required %s", coin.Reserve().String(), commissionInBaseCoin.String()), - Info: EncodeError(map[string]string{ - "has_reserve": coin.Reserve().String(), - "commission": commissionInBaseCoin.String(), - "gas_coin": coin.CName, - }), - } - } - - commission = formula.CalculateSaleAmount(coin.Volume(), coin.Reserve(), coin.Crr(), commissionInBaseCoin) + commission = formula.CalculateSaleAmount(gasCoin.Volume(), gasCoin.Reserve(), gasCoin.Crr(), commissionInBaseCoin) } - if context.Accounts.GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { + if checkState.Accounts().GetBalance(sender, tx.GasCoin).Cmp(commission) < 0 { return Response{ Code: code.InsufficientFunds, - Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, tx.GasCoin), - Info: EncodeError(map[string]string{ - "sender": sender.String(), - "needed_value": commission.String(), - "gas_coin": fmt.Sprintf("%s", tx.GasCoin), - }), + Log: fmt.Sprintf("Insufficient funds for sender account: %s. Wanted %s %s", sender.String(), commission, gasCoin.GetFullSymbol()), + Info: EncodeError(code.NewInsufficientFunds(sender.String(), commission.String(), gasCoin.GetFullSymbol(), gasCoin.ID().String())), } } - if !isCheck { + if deliverState, ok := context.(*state.State); ok { // now + 30 days unbondAtBlock := currentBlock + unbondPeriod rewardPool.Add(rewardPool, commissionInBaseCoin) - context.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) - context.Coins.SubVolume(tx.GasCoin, commission) + deliverState.Coins.SubReserve(tx.GasCoin, commissionInBaseCoin) + deliverState.Coins.SubVolume(tx.GasCoin, commission) + + deliverState.Accounts.SubBalance(sender, tx.GasCoin, commission) + + if waitList := deliverState.Waitlist.Get(sender, data.PubKey, data.Coin); waitList != nil { + diffValue := big.NewInt(0).Sub(data.Value, waitList.Value) + deliverState.Waitlist.Delete(sender, data.PubKey, data.Coin) + if diffValue.Sign() == -1 { + deliverState.Waitlist.AddWaitList(sender, data.PubKey, data.Coin, big.NewInt(0).Neg(diffValue)) + } + } else { + deliverState.Candidates.SubStake(sender, data.PubKey, data.Coin, data.Value) + } - context.Accounts.SubBalance(sender, tx.GasCoin, commission) - context.Candidates.SubStake(sender, data.PubKey, data.Coin, data.Value) - context.FrozenFunds.AddFund(unbondAtBlock, sender, data.PubKey, data.Coin, data.Value) - context.Accounts.SetNonce(sender, tx.Nonce) + deliverState.FrozenFunds.AddFund(unbondAtBlock, sender, data.PubKey, deliverState.Candidates.ID(data.PubKey), data.Coin, data.Value) + deliverState.Accounts.SetNonce(sender, tx.Nonce) } tags := kv.Pairs{ diff --git a/core/transaction/unbond_test.go b/core/transaction/unbond_test.go index 2162e1341..557d0d68e 100644 --- a/core/transaction/unbond_test.go +++ b/core/transaction/unbond_test.go @@ -1,11 +1,14 @@ package transaction import ( + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/state" + "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" - "github.com/MinterTeam/minter-go-node/upgrades" + "math/big" "sync" "testing" @@ -18,14 +21,14 @@ func TestUnbondTx(t *testing.T) { privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) - coin := types.GetBaseCoin() + coin := types.GetBaseCoinID() cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) value := helpers.BipToPip(big.NewInt(100)) cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) - cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock3) + cState.Candidates.RecalculateStakes(109000) data := UnbondData{ PubKey: pubkey, @@ -59,13 +62,13 @@ func TestUnbondTx(t *testing.T) { t.Fatal(err) } - response := RunTx(cState, false, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) if response.Code != 0 { t.Fatalf("Response code is not 0. Error %s", response.Log) } - cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock3) + cState.Candidates.RecalculateStakes(109000) targetBalance, _ := big.NewInt(0).SetString("999999800000000000000000", 10) balance := cState.Accounts.GetBalance(addr, coin) @@ -78,4 +81,530 @@ func TestUnbondTx(t *testing.T) { if stake.Value.Cmp(types.Big0) != 0 { t.Fatalf("Stake value is not corrent. Expected %s, got %s", types.Big0, stake.Value) } + + checkState(t, cState) +} + +func TestFullUnbondTxWithWaitlist(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + waitlistAmount := helpers.BipToPip(big.NewInt(1000)) + value := helpers.BipToPip(big.NewInt(1000)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Waitlist.AddWaitList(addr, pubkey, coin, waitlistAmount) + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + cState.Candidates.RecalculateStakes(109000) + funds := cState.FrozenFunds.GetFrozenFunds(candidates.UnbondPeriod) + if funds == nil || len(funds.List) != 1 { + t.Fatalf("Frozen funds are not correct") + } + + stake := cState.Candidates.GetStakeOfAddress(pubkey, addr, coin) + if stake != nil { + t.Fatalf("Stake value is not empty.") + } + + if funds.List[0].Value.Cmp(value) != 0 { + t.Fatalf("Frozen funds value is not corrent. Expected %s, got %s", value, funds.List[0].Value) + } + + wl := cState.Waitlist.Get(addr, pubkey, coin) + if wl != nil { + t.Fatalf("Waitlist is not deleted") + } + + checkState(t, cState) +} + +func TestUnbondTxWithWaitlist(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + waitlistAmount := helpers.BipToPip(big.NewInt(1000)) + unbondAmount := helpers.BipToPip(big.NewInt(50)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Waitlist.AddWaitList(addr, pubkey, coin, waitlistAmount) + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: unbondAmount, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != 0 { + t.Fatalf("Response code is not 0. Error %s", response.Log) + } + + cState.Candidates.RecalculateStakes(109000) + funds := cState.FrozenFunds.GetFrozenFunds(candidates.UnbondPeriod) + if funds == nil || len(funds.List) != 1 { + t.Fatalf("Frozen funds are not correct") + } + + if funds.List[0].Value.Cmp(unbondAmount) != 0 { + t.Fatalf("Frozen funds value is not corrent. Expected %s, got %s", unbondAmount, funds.List[0].Value) + } + + wl := cState.Waitlist.Get(addr, pubkey, coin) + if wl == nil { + t.Fatalf("Waitlist is empty") + } + + amount := new(big.Int).Sub(waitlistAmount, unbondAmount) + if wl.Value.Cmp(amount) != 0 { + t.Fatalf("Waitlist is not correct") + } + + checkState(t, cState) +} + +func TestUnbondTxToDecodeError(t *testing.T) { + cState := getState() + + pubkey := createTestCandidate(cState) + + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + + value := helpers.BipToPip(big.NewInt(100)) + cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) + + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + response := data.Run(&tx, state.NewCheckState(cState), nil, 1) + if response.Code != code.DecodeError { + t.Fatalf("Response code is not %d. Error %s", code.DecodeError, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToNotExistCoin(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + data := UnbondData{ + PubKey: pubkey, + Coin: 5, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinNotExists { + t.Fatalf("Response code is not %d. Error %s", code.CoinNotExists, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToNotExistCandidate(t *testing.T) { + cState := getState() + pubkey := types.Pubkey{1} + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CandidateNotFound { + t.Fatalf("Response code is not %d. Error %s", code.CandidateNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToNotExistStake(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.StakeNotFound { + t.Fatalf("Response code is not %d. Error %s", code.StakeNotFound, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToInsufficientStake(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + cState.Accounts.AddBalance(addr, coin, helpers.BipToPip(big.NewInt(1000000))) + cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: helpers.BipToPip(big.NewInt(1000)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientStake { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientStake, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToInsufficientFunds(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientFunds { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientFunds, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToInsufficientAmountAtWaitlist(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + cState.Waitlist.AddWaitList(addr, pubkey, coin, value) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: helpers.BipToPip(big.NewInt(1000)), + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: coin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.InsufficientWaitList { + t.Fatalf("Response code is not %d. Error %s", code.InsufficientWaitList, response.Log) + } + + checkState(t, cState) +} + +func TestUnbondTxToGasCoinReserveUnderflow(t *testing.T) { + cState := getState() + pubkey := createTestCandidate(cState) + privateKey, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(privateKey.PublicKey) + coin := types.GetBaseCoinID() + value := helpers.BipToPip(big.NewInt(100)) + + cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) + cState.Candidates.RecalculateStakes(109000) + + data := UnbondData{ + PubKey: pubkey, + Coin: coin, + Value: value, + } + + encodedData, err := rlp.EncodeToBytes(data) + if err != nil { + t.Fatal(err) + } + + gasCoin := createTestCoin(cState) + cState.Coins.SubReserve(gasCoin, helpers.BipToPip(big.NewInt(90000))) + + tx := Transaction{ + Nonce: 1, + GasPrice: 1, + ChainID: types.CurrentChainID, + GasCoin: gasCoin, + Type: TypeUnbond, + Data: encodedData, + SignatureType: SigTypeSingle, + } + + if err := tx.Sign(privateKey); err != nil { + t.Fatal(err) + } + + encodedTx, err := rlp.EncodeToBytes(tx) + if err != nil { + t.Fatal(err) + } + + response := RunTx(cState, encodedTx, big.NewInt(0), 0, &sync.Map{}, 0) + if response.Code != code.CoinReserveUnderflow { + t.Fatalf("Response code is not %d. Error %s", code.CoinReserveUnderflow, response.Log) + } + + checkState(t, cState) } diff --git a/core/types/appstate.go b/core/types/appstate.go index e0e928aa4..2bfcbd518 100644 --- a/core/types/appstate.go +++ b/core/types/appstate.go @@ -8,16 +8,19 @@ import ( ) type AppState struct { - Note string `json:"note"` - StartHeight uint64 `json:"start_height"` - Validators []Validator `json:"validators,omitempty"` - Candidates []Candidate `json:"candidates,omitempty"` - Accounts []Account `json:"accounts,omitempty"` - Coins []Coin `json:"coins,omitempty"` - FrozenFunds []FrozenFund `json:"frozen_funds,omitempty"` - UsedChecks []UsedCheck `json:"used_checks,omitempty"` - MaxGas uint64 `json:"max_gas"` - TotalSlashed string `json:"total_slashed"` + Note string `json:"note"` + StartHeight uint64 `json:"start_height"` + Validators []Validator `json:"validators,omitempty"` + Candidates []Candidate `json:"candidates,omitempty"` + BlockListCandidates []Pubkey `json:"block_list_candidates,omitempty"` + Waitlist []Waitlist `json:"waitlist,omitempty"` + Accounts []Account `json:"accounts,omitempty"` + Coins []Coin `json:"coins,omitempty"` + FrozenFunds []FrozenFund `json:"frozen_funds,omitempty"` + HaltBlocks []HaltBlock `json:"halt_blocks,omitempty"` + UsedChecks []UsedCheck `json:"used_checks,omitempty"` + MaxGas uint64 `json:"max_gas"` + TotalSlashed string `json:"total_slashed"` } func (s *AppState) Verify() error { @@ -79,18 +82,20 @@ func (s *AppState) Verify() error { return fmt.Errorf("not valid balance for account %s", acc.Address.String()) } - if !bal.Coin.IsBaseCoin() { + coinID := CoinID(bal.Coin) + if !coinID.IsBaseCoin() { // check not existing coins foundCoin := false for _, coin := range s.Coins { - if coin.Symbol == bal.Coin { + id := CoinID(coin.ID) + if id == coinID { foundCoin = true break } } if !foundCoin { - return fmt.Errorf("coin %s not found", bal.Coin) + return fmt.Errorf("coin %s not found", coinID) } } } @@ -100,52 +105,61 @@ func (s *AppState) Verify() error { stakes := map[string]struct{}{} for _, stake := range candidate.Stakes { // check duplicated stakes - key := fmt.Sprintf("%s:%s", stake.Owner.String(), stake.Coin.String()) + coinID := CoinID(stake.Coin) + key := fmt.Sprintf("%s:%s", stake.Owner.String(), coinID.String()) if _, exists := stakes[key]; exists { return fmt.Errorf("duplicated stake %s", key) } stakes[key] = struct{}{} // check not existing coins - if !stake.Coin.IsBaseCoin() { + if !coinID.IsBaseCoin() { foundCoin := false for _, coin := range s.Coins { - if coin.Symbol == stake.Coin { + id := CoinID(coin.ID) + if id == coinID { foundCoin = true break } } if !foundCoin { - return fmt.Errorf("coin %s not found", stake.Coin) + return fmt.Errorf("coin %s not found", coinID) } } } } - coins := map[CoinSymbol]struct{}{} + coins := map[uint64]struct{}{} for _, coin := range s.Coins { if coin.Symbol.IsBaseCoin() { return fmt.Errorf("base coin should not be declared") } // check duplicated coins - if _, exists := coins[coin.Symbol]; exists { + if _, exists := coins[coin.ID]; exists { return fmt.Errorf("duplicated coin %s", coin.Symbol) } - coins[coin.Symbol] = struct{}{} + + coins[coin.ID] = struct{}{} // check coins' volume volume := big.NewInt(0) for _, ff := range s.FrozenFunds { - if ff.Coin == coin.Symbol { + if ff.Coin == coin.ID { volume.Add(volume, helpers.StringToBigInt(ff.Value)) } } for _, candidate := range s.Candidates { for _, stake := range candidate.Stakes { - if stake.Coin == coin.Symbol { + if stake.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(stake.Value)) + } + } + + for _, stake := range candidate.Updates { + if stake.Coin == coin.ID { volume.Add(volume, helpers.StringToBigInt(stake.Value)) } } @@ -153,14 +167,42 @@ func (s *AppState) Verify() error { for _, account := range s.Accounts { for _, bal := range account.Balance { - if bal.Coin == coin.Symbol { + if bal.Coin == coin.ID { volume.Add(volume, helpers.StringToBigInt(bal.Value)) } } } + for _, wl := range s.Waitlist { + if wl.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(wl.Value)) + } + } + if volume.Cmp(helpers.StringToBigInt(coin.Volume)) != 0 { - return fmt.Errorf("wrong coin %s volume", coin.Symbol.String()) + return fmt.Errorf("wrong coin %s volume (%s)", coin.Symbol.String(), big.NewInt(0).Sub(volume, helpers.StringToBigInt(coin.Volume))) + } + } + + for _, wl := range s.Waitlist { + if !helpers.IsValidBigInt(wl.Value) { + return fmt.Errorf("wrong waitlist value: %s", wl.Value) + } + + // check not existing coins + coinID := CoinID(wl.Coin) + if !coinID.IsBaseCoin() { + foundCoin := false + for _, coin := range s.Coins { + if CoinID(coin.ID) == coinID { + foundCoin = true + break + } + } + + if !foundCoin { + return fmt.Errorf("coin %s not found", coinID) + } } } @@ -170,17 +212,19 @@ func (s *AppState) Verify() error { } // check not existing coins - if !ff.Coin.IsBaseCoin() { + coinID := CoinID(ff.Coin) + if !coinID.IsBaseCoin() { foundCoin := false for _, coin := range s.Coins { - if coin.Symbol == ff.Coin { + id := CoinID(coin.ID) + if id == coinID { foundCoin = true break } } if !foundCoin { - return fmt.Errorf("coin %s not found", ff.Coin) + return fmt.Errorf("coin %s not found", coinID) } } } @@ -202,44 +246,57 @@ func (s *AppState) Verify() error { type Validator struct { TotalBipStake string `json:"total_bip_stake"` - PubKey Pubkey `json:"pub_key"` + PubKey Pubkey `json:"public_key"` AccumReward string `json:"accum_reward"` AbsentTimes *BitArray `json:"absent_times"` } type Candidate struct { - RewardAddress Address `json:"reward_address"` - OwnerAddress Address `json:"owner_address"` - TotalBipStake string `json:"total_bip_stake"` - PubKey Pubkey `json:"pub_key"` - Commission uint `json:"commission"` - Stakes []Stake `json:"stakes"` - Updates []Stake `json:"updates"` - Status byte `json:"status"` + ID uint64 `json:"id"` + RewardAddress Address `json:"reward_address"` + OwnerAddress Address `json:"owner_address"` + ControlAddress Address `json:"control_address"` + TotalBipStake string `json:"total_bip_stake"` + PubKey Pubkey `json:"public_key"` + Commission uint64 `json:"commission"` + Stakes []Stake `json:"stakes"` + Updates []Stake `json:"updates"` + Status uint64 `json:"status"` } type Stake struct { - Owner Address `json:"owner"` - Coin CoinSymbol `json:"coin"` - Value string `json:"value"` - BipValue string `json:"bip_value"` + Owner Address `json:"owner"` + Coin uint64 `json:"coin"` + Value string `json:"value"` + BipValue string `json:"bip_value"` +} + +type Waitlist struct { + CandidateID uint64 `json:"candidate_id"` + Owner Address `json:"owner"` + Coin uint64 `json:"coin"` + Value string `json:"value"` } type Coin struct { - Name string `json:"name"` - Symbol CoinSymbol `json:"symbol"` - Volume string `json:"volume"` - Crr uint `json:"crr"` - Reserve string `json:"reserve"` - MaxSupply string `json:"max_supply"` + ID uint64 `json:"id"` + Name string `json:"name"` + Symbol CoinSymbol `json:"symbol"` + Volume string `json:"volume"` + Crr uint64 `json:"crr"` + Reserve string `json:"reserve"` + MaxSupply string `json:"max_supply"` + Version uint64 `json:"version"` + OwnerAddress *Address `json:"owner_address"` } type FrozenFund struct { - Height uint64 `json:"height"` - Address Address `json:"address"` - CandidateKey *Pubkey `json:"candidate_key,omitempty"` - Coin CoinSymbol `json:"coin"` - Value string `json:"value"` + Height uint64 `json:"height"` + Address Address `json:"address"` + CandidateKey *Pubkey `json:"candidate_key,omitempty"` + CandidateID uint64 `json:"candidate_id"` + Coin uint64 `json:"coin"` + Value string `json:"value"` } type UsedCheck string @@ -252,12 +309,17 @@ type Account struct { } type Balance struct { - Coin CoinSymbol `json:"coin"` - Value string `json:"value"` + Coin uint64 `json:"coin"` + Value string `json:"value"` } type Multisig struct { - Weights []uint `json:"weights"` - Threshold uint `json:"threshold"` + Weights []uint64 `json:"weights"` + Threshold uint64 `json:"threshold"` Addresses []Address `json:"addresses"` } + +type HaltBlock struct { + Height uint64 `json:"height"` + CandidateKey Pubkey `json:"candidate_key"` +} diff --git a/core/types/bitarray.go b/core/types/bitarray.go index 5cc54cc32..1b6fd35d9 100644 --- a/core/types/bitarray.go +++ b/core/types/bitarray.go @@ -189,6 +189,6 @@ func (bA *BitArray) UnmarshalJSON(bz []byte) error { bA2.SetIndex(i, true) } } - *bA = *bA2 + *bA = *bA2 //nolint:govet return nil } diff --git a/core/types/bitarray_test.go b/core/types/bitarray_test.go new file mode 100644 index 000000000..c2375259e --- /dev/null +++ b/core/types/bitarray_test.go @@ -0,0 +1,45 @@ +package types + +import ( + "bytes" + "testing" +) + +func TestNewBitArray(t *testing.T) { + if NewBitArray(0) != nil { + t.Error("bit array is not nil") + } +} + +func TestBitArraySize(t *testing.T) { + b := NewBitArray(10) + if b.Size() != 10 { + t.Error("incorrect size of bit array") + } + + b = NewBitArray(0) + if b.Size() != 0 { + t.Error("incorrect size of bit array") + } +} + +func TestBitArrayGetIndex(t *testing.T) { + b := NewBitArray(0) + if b.GetIndex(10) != false { + t.Error("invalid index of bit array") + } +} + +func TestBitArraySetIndex(t *testing.T) { + b := NewBitArray(0) + if b.SetIndex(10, true) != false { + t.Error("invalid index of bit array") + } +} + +func TestBitArrayBytes(t *testing.T) { + b := NewBitArray(10) + if !bytes.Equal(b.Bytes(), []byte{0, 0}) { + t.Error("Bytes are not equal") + } +} diff --git a/core/types/bytes_test.go b/core/types/bytes_test.go index 4f4187875..dea1f0d28 100644 --- a/core/types/bytes_test.go +++ b/core/types/bytes_test.go @@ -19,41 +19,44 @@ package types import ( "bytes" "testing" - - checker "gopkg.in/check.v1" ) -type BytesSuite struct{} - -var _ = checker.Suite(&BytesSuite{}) - -func (s *BytesSuite) TestCopyBytes(c *checker.C) { +func TestCopyBytes(t *testing.T) { data1 := []byte{1, 2, 3, 4} exp1 := []byte{1, 2, 3, 4} res1 := CopyBytes(data1) - c.Assert(res1, checker.DeepEquals, exp1) + + if !bytes.Equal(res1, exp1) { + t.Error("Bytes are not the same") + } + + if CopyBytes(nil) != nil { + t.Error("Incorrect result of copy bytes") + } } -func (s *BytesSuite) TestLeftPadBytes(c *checker.C) { +func TestLeftPadBytes(t *testing.T) { val1 := []byte{1, 2, 3, 4} exp1 := []byte{0, 0, 0, 0, 1, 2, 3, 4} res1 := LeftPadBytes(val1, 8) res2 := LeftPadBytes(val1, 2) - c.Assert(res1, checker.DeepEquals, exp1) - c.Assert(res2, checker.DeepEquals, val1) + if !bytes.Equal(res1, exp1) || !bytes.Equal(res2, val1) { + t.Error("Bytes are not the same") + } } -func (s *BytesSuite) TestRightPadBytes(c *checker.C) { +func TestRightPadBytes(t *testing.T) { val := []byte{1, 2, 3, 4} exp := []byte{1, 2, 3, 4, 0, 0, 0, 0} resstd := RightPadBytes(val, 8) resshrt := RightPadBytes(val, 2) - c.Assert(resstd, checker.DeepEquals, exp) - c.Assert(resshrt, checker.DeepEquals, val) + if !bytes.Equal(resstd, exp) || !bytes.Equal(resshrt, val) { + t.Error("Bytes are not the same") + } } func TestFromHex(t *testing.T) { @@ -103,3 +106,40 @@ func TestNoPrefixShortHexOddLength(t *testing.T) { t.Errorf("Expected %x got %x", expected, result) } } + +func TestToHex(t *testing.T) { + b := []byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + if ToHex(b, "Mx") != "Mx0102030405000000000000000000000000000000" { + t.Error("Incorrect hex representation") + } + + if ToHex(nil, "Mx") != "Mx0" { + t.Error("Incorrect hex representation") + } +} + +func TestBytes2Hex(t *testing.T) { + if Bytes2Hex([]byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) != "0102030405000000000000000000000000000000" { + t.Error("Incorrect hex representation") + } +} + +func TestHex2BytesFixed(t *testing.T) { + b := []byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + b2 := Hex2BytesFixed("0102030405000000000000000000000000000000", 20) + if !bytes.Equal(b2, b) { + t.Error("Incorrect hex representation") + } + + b = []byte{0, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + b2 = Hex2BytesFixed("0102030405000000000000000000000000000000", 21) + if !bytes.Equal(b2, b) { + t.Error("Incorrect hex representation") + } + + b = []byte{2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + b2 = Hex2BytesFixed("0102030405000000000000000000000000000000", 19) + if !bytes.Equal(b2, b) { + t.Error("Incorrect hex representation") + } +} diff --git a/core/types/constants.go b/core/types/constants.go index d40591857..e4272bd87 100644 --- a/core/types/constants.go +++ b/core/types/constants.go @@ -1,23 +1,33 @@ package types +// ChainID is ID of the network (1 - mainnet, 2 - testnet) type ChainID byte const ( - ChainTestnet = 0x02 - ChainMainnet = 0x01 - - CurrentChainID = ChainMainnet + // ChainMainnet is mainnet chain ID of the network + ChainMainnet ChainID = 0x01 + // ChainTestnet is mainnet chain ID of the network + ChainTestnet ChainID = 0x02 ) +// CurrentChainID is current ChainID of the network +var CurrentChainID = ChainMainnet + var ( coinTestnet = StrToCoinSymbol("MNT") coinMainnet = StrToCoinSymbol("BIP") ) +// GetBaseCoin returns the coin symbol of the current ChainID func GetBaseCoin() CoinSymbol { return getBaseCoin(CurrentChainID) } +// GetBaseCoinID returns ID of base coin +func GetBaseCoinID() CoinID { + return BasecoinID +} + func getBaseCoin(chainID ChainID) CoinSymbol { switch chainID { case ChainMainnet: diff --git a/core/types/constants_test.go b/core/types/constants_test.go new file mode 100644 index 000000000..35ed70db4 --- /dev/null +++ b/core/types/constants_test.go @@ -0,0 +1,23 @@ +package types + +import ( + "testing" +) + +func TestGetBaseCoin(t *testing.T) { + CurrentChainID = ChainTestnet + if GetBaseCoin().Compare(coinTestnet) != 0 { + t.Error("Incorrect base coin") + } + + CurrentChainID = ChainMainnet + if GetBaseCoin().Compare(coinMainnet) != 0 { + t.Error("Incorrect base coin") + } +} + +func TestGetBaseCoinID(t *testing.T) { + if GetBaseCoinID() != BasecoinID { + t.Error("Incorrect base coin id") + } +} diff --git a/core/types/size_test.go b/core/types/size_test.go index cf2d72681..f4f031d64 100644 --- a/core/types/size_test.go +++ b/core/types/size_test.go @@ -36,3 +36,20 @@ func TestStorageSizeString(t *testing.T) { } } } + +func TestStorageSizeToTerminalString(t *testing.T) { + tests := []struct { + size StorageSize + str string + }{ + {2381273, "2.38mB"}, + {2192, "2.19kB"}, + {12, "12.00B"}, + } + + for _, test := range tests { + if test.size.TerminalString() != test.str { + t.Errorf("%f: got %q, want %q", float64(test.size), test.size.TerminalString(), test.str) + } + } +} diff --git a/core/types/types.go b/core/types/types.go index 848f71f73..d604e7628 100644 --- a/core/types/types.go +++ b/core/types/types.go @@ -2,19 +2,29 @@ package types import ( "bytes" + "encoding/binary" "encoding/hex" "fmt" "github.com/MinterTeam/minter-go-node/hexutil" "math/big" "math/rand" "reflect" + "strconv" + "strings" ) +// Types lengths const ( - HashLength = 32 - AddressLength = 20 - PubKeyLength = 32 - CoinSymbolLength = 10 + HashLength = 32 + AddressLength = 20 + PubKeyLength = 32 + CoinSymbolLength = 10 + TendermintAddressLength = 20 +) + +const ( + // BasecoinID is an ID of a base coin + BasecoinID = 0 ) var ( @@ -25,20 +35,24 @@ var ( // Hash represents the 32 byte Keccak256 hash of arbitrary data. type Hash [HashLength]byte +// BytesToHash converts given byte slice to Hash func BytesToHash(b []byte) Hash { var h Hash h.SetBytes(b) return h } -func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } -func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } -func HexToHash(s string) Hash { return BytesToHash(FromHex(s, "Mh")) } -// Get the string representation of the underlying hash -func (h Hash) Str() string { return string(h[:]) } +// Str returns the string representation of the underlying hash +func (h Hash) Str() string { return string(h[:]) } + +// Bytes returns the bytes representation of the underlying hash func (h Hash) Bytes() []byte { return h[:] } + +// Big returns the big.Int representation of the underlying hash func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } -func (h Hash) Hex() string { return hexutil.Encode(h[:]) } + +// Hex returns the hex-string representation of the underlying hash +func (h Hash) Hex() string { return hexutil.Encode(h[:]) } // TerminalString implements log.TerminalStringer, formatting a string for console // output during logging. @@ -73,7 +87,7 @@ func (h Hash) MarshalText() ([]byte, error) { return hexutil.Bytes(h[:]).MarshalText() } -// Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left). +// SetBytes Sets the hash to the value of b. If b is larger than len(h), 'b' will be cropped (from the left). func (h *Hash) SetBytes(b []byte) { if len(b) > len(h) { b = b[len(b)-HashLength:] @@ -82,10 +96,10 @@ func (h *Hash) SetBytes(b []byte) { copy(h[HashLength-len(b):], b) } -// Set string `s` to h. If s is larger than len(h) s will be cropped (from left) to fit. +// SetString sets string `s` to h. If s is larger than len(h) s will be cropped (from left) to fit. func (h *Hash) SetString(s string) { h.SetBytes([]byte(s)) } -// Sets h to other +// Set h to other func (h *Hash) Set(other Hash) { for i, v := range other { h[i] = v @@ -101,6 +115,7 @@ func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value { return reflect.ValueOf(h) } +// EmptyHash checks if given Hash is empty func EmptyHash(h Hash) bool { return h == Hash{} } @@ -120,11 +135,15 @@ func (h UnprefixedHash) MarshalText() ([]byte, error) { /////////// Coin +// CoinSymbol represents the 10 byte coin symbol. type CoinSymbol [CoinSymbolLength]byte func (c CoinSymbol) String() string { return string(bytes.Trim(c[:], "\x00")) } -func (c CoinSymbol) Bytes() []byte { return c[:] } +// Bytes returns the bytes representation of the underlying CoinSymbol +func (c CoinSymbol) Bytes() []byte { return c[:] } + +// MarshalJSON encodes coin to json func (c CoinSymbol) MarshalJSON() ([]byte, error) { buffer := bytes.NewBufferString("\"") @@ -134,37 +153,103 @@ func (c CoinSymbol) MarshalJSON() ([]byte, error) { return buffer.Bytes(), nil } +// UnmarshalJSON parses a coinSymbol from json func (c *CoinSymbol) UnmarshalJSON(input []byte) error { *c = StrToCoinSymbol(string(input[1 : len(input)-1])) return nil } +// Compare compares coin symbols. +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. func (c CoinSymbol) Compare(c2 CoinSymbol) int { return bytes.Compare(c.Bytes(), c2.Bytes()) } +// IsBaseCoin checks if coin is a base coin func (c CoinSymbol) IsBaseCoin() bool { return c.Compare(GetBaseCoin()) == 0 } +// StrToCoinSymbol converts given string to a coin symbol func StrToCoinSymbol(s string) CoinSymbol { var symbol CoinSymbol - copy(symbol[:], []byte(s)) + copy(symbol[:], s) return symbol } +// StrToCoinBaseSymbol converts give string to a coin base symbol +func StrToCoinBaseSymbol(s string) CoinSymbol { + delimiter := strings.Index(s, "-") + if delimiter != -1 { + return StrToCoinSymbol(s[:delimiter]) + } + + return StrToCoinSymbol(s) +} + +// GetVersionFromSymbol returns coin version extracted from symbol +func GetVersionFromSymbol(s string) CoinVersion { + parts := strings.Split(s, "-") + if len(parts) == 1 { + return 0 + } + + v, _ := strconv.ParseUint(parts[1], 10, 16) + return CoinVersion(v) +} + +// CoinID represents coin id +type CoinID uint32 + +// IsBaseCoin checks if +func (c CoinID) IsBaseCoin() bool { + return c == GetBaseCoinID() +} + +func (c CoinID) String() string { + return strconv.Itoa(int(c)) +} + +// Bytes returns LittleEndian encoded bytes of coin id +func (c CoinID) Bytes() []byte { + b := make([]byte, 4) + binary.LittleEndian.PutUint32(b, c.Uint32()) + return b +} + +// Uint32 returns coin id as uint32 +func (c CoinID) Uint32() uint32 { + return uint32(c) +} + +// BytesToCoinID converts bytes to coin id. Expects LittleEndian encoding. +func BytesToCoinID(bytes []byte) CoinID { + return CoinID(binary.LittleEndian.Uint32(bytes)) +} + +// CoinVersion represents coin version info +type CoinVersion = uint16 + /////////// Address +// Address represents 20-byte address in Minter Blockchain type Address [AddressLength]byte +// BytesToAddress converts given byte slice to Address func BytesToAddress(b []byte) Address { var a Address a.SetBytes(b) return a } + +// StringToAddress converts given string to Address func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } -func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } -func HexToAddress(s string) Address { return BytesToAddress(FromHex(s, "Mx")) } + +// BigToAddress converts given big.Int to Address +func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) } + +// HexToAddress converts given hex string to Address +func HexToAddress(s string) Address { return BytesToAddress(FromHex(s, "Mx")) } // IsHexAddress verifies whether a string can represent a valid hex-encoded // Minter address or not. @@ -175,12 +260,19 @@ func IsHexAddress(s string) bool { return len(s) == 2*AddressLength && isHex(s) } -// Get the string representation of the underlying address -func (a Address) Str() string { return string(a[:]) } +// Str returns the string representation of the underlying address +func (a Address) Str() string { return string(a[:]) } + +// Bytes returns the byte representation of the underlying address func (a Address) Bytes() []byte { return a[:] } + +// Big returns the big.Int representation of the underlying address func (a Address) Big() *big.Int { return new(big.Int).SetBytes(a[:]) } -func (a Address) Hash() Hash { return BytesToHash(a[:]) } +// Hash returns the Hash representation of the underlying address +func (a Address) Hash() Hash { return BytesToHash(a[:]) } + +// Hex returns the hex-string representation of the underlying address func (a Address) Hex() string { return "Mx" + hex.EncodeToString(a[:]) } @@ -196,7 +288,7 @@ func (a Address) Format(s fmt.State, c rune) { fmt.Fprintf(s, "%"+string(c), a[:]) } -// Sets the address to the value of b. If b is larger than len(a) it will panic +// SetBytes Sets the address to the value of b. If b is larger than len(a) it will panic func (a *Address) SetBytes(b []byte) { if len(b) > len(a) { b = b[len(b)-AddressLength:] @@ -204,10 +296,10 @@ func (a *Address) SetBytes(b []byte) { copy(a[AddressLength-len(b):], b) } -// Set string `s` to a. If s is larger than len(a) it will panic +// SetString set string `s` to a. If s is larger than len(a) it will panic func (a *Address) SetString(s string) { a.SetBytes([]byte(s)) } -// Sets a to other +// Set Sets a to other func (a *Address) Set(other Address) { for i, v := range other { a[i] = v @@ -224,11 +316,13 @@ func (a *Address) UnmarshalText(input []byte) error { return hexutil.UnmarshalFixedText("Address", input, a[:]) } +// Unmarshal parses a hash from byte slice. func (a *Address) Unmarshal(input []byte) error { copy(a[:], input) return nil } +// MarshalJSON marshals given address to json format. func (a Address) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("\"%s\"", a.String())), nil } @@ -238,33 +332,26 @@ func (a *Address) UnmarshalJSON(input []byte) error { return hexutil.UnmarshalFixedJSON(addressT, input, a[:]) } +// Compare compares addresses. +// The result will be 0 if a==b, -1 if a < b, and +1 if a > b. func (a *Address) Compare(a2 Address) int { return bytes.Compare(a.Bytes(), a2.Bytes()) } -// UnprefixedHash allows marshaling an Address without 0x prefix. -type UnprefixedAddress Address - -// UnmarshalText decodes the address from hex. The 0x prefix is optional. -func (a *UnprefixedAddress) UnmarshalText(input []byte) error { - return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:]) -} - -// MarshalText encodes the address as hex. -func (a UnprefixedAddress) MarshalText() ([]byte, error) { - return []byte(hex.EncodeToString(a[:])), nil -} - -type Pubkey [32]byte +// Pubkey represents 32 byte public key of a validator +type Pubkey [PubKeyLength]byte +// HexToPubkey decodes given string into Pubkey func HexToPubkey(s string) Pubkey { return BytesToPubkey(FromHex(s, "Mp")) } +// BytesToPubkey decodes given bytes into Pubkey func BytesToPubkey(b []byte) Pubkey { var p Pubkey p.SetBytes(b) return p } +// SetBytes sets given bytes as public key func (p *Pubkey) SetBytes(b []byte) { if len(b) > len(p) { b = b[len(b)-PubKeyLength:] @@ -272,20 +359,24 @@ func (p *Pubkey) SetBytes(b []byte) { copy(p[PubKeyLength-len(b):], b) } +// Bytes returns underlying bytes func (p Pubkey) Bytes() []byte { return p[:] } func (p Pubkey) String() string { return fmt.Sprintf("Mp%x", p[:]) } +// MarshalText encodes Pubkey from to text. func (p Pubkey) MarshalText() ([]byte, error) { return []byte(p.String()), nil } +// MarshalJSON encodes Pubkey from to json format. func (p Pubkey) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf("\"%s\"", p.String())), nil } +// UnmarshalJSON decodes Pubkey from json format. func (p *Pubkey) UnmarshalJSON(input []byte) error { b, err := hex.DecodeString(string(input)[3 : len(input)-1]) copy(p[:], b) @@ -293,8 +384,10 @@ func (p *Pubkey) UnmarshalJSON(input []byte) error { return err } +// Equals checks if public keys are equal func (p Pubkey) Equals(p2 Pubkey) bool { return p == p2 } -type TmAddress [20]byte +// TmAddress represents Tendermint address +type TmAddress [TendermintAddressLength]byte diff --git a/core/types/types_test.go b/core/types/types_test.go index c2365c12e..48cfe3f8d 100644 --- a/core/types/types_test.go +++ b/core/types/types_test.go @@ -19,6 +19,7 @@ package types import ( "bytes" "encoding/json" + "github.com/MinterTeam/minter-go-node/helpers" "github.com/tendermint/go-amino" "math/big" "strings" @@ -153,7 +154,7 @@ func TestAppState(t *testing.T) { Stakes: []Stake{ { Owner: testAddr, - Coin: GetBaseCoin(), + Coin: uint64(GetBaseCoinID()), Value: big.NewInt(1).String(), BipValue: big.NewInt(1).String(), }, @@ -166,13 +167,17 @@ func TestAppState(t *testing.T) { Address: testAddr, Balance: []Balance{ { - Coin: GetBaseCoin(), + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + }, + { + Coin: uint64(GetBaseCoinID() + 1), Value: big.NewInt(1).String(), }, }, Nonce: 1, MultisigData: &Multisig{ - Weights: []uint{1, 2, 3}, + Weights: []uint64{1, 2, 3}, Threshold: 1, Addresses: []Address{testAddr, testAddr}, }, @@ -180,11 +185,13 @@ func TestAppState(t *testing.T) { }, Coins: []Coin{ { - Name: "ASD", - Symbol: GetBaseCoin(), - Volume: big.NewInt(1).String(), - Crr: 1, - Reserve: big.NewInt(1).String(), + ID: uint64(GetBaseCoinID() + 1), + Name: "ASD", + Symbol: StrToCoinSymbol("TEST"), + Volume: big.NewInt(2).String(), + Crr: 1, + Reserve: helpers.BipToPip(big.NewInt(100000)).String(), + MaxSupply: helpers.BipToPip(big.NewInt(100000)).String(), }, }, FrozenFunds: []FrozenFund{ @@ -192,18 +199,23 @@ func TestAppState(t *testing.T) { Height: 1, Address: testAddr, CandidateKey: &pubkey, - Coin: GetBaseCoin(), + Coin: uint64(GetBaseCoinID() + 1), Value: big.NewInt(1).String(), }, }, UsedChecks: []UsedCheck{ - "123", + "00004601d10c33eda76bb16a54a0d8882a57ec34e964aa23e2b5d9aa10957fee", }, - MaxGas: 10, + MaxGas: 10, + TotalSlashed: big.NewInt(1e18).String(), } cdc := amino.NewCodec() + if err := appState.Verify(); err != nil { + t.Error(err) + } + b1, err := cdc.MarshalJSON(appState) if err != nil { panic(err) @@ -220,7 +232,542 @@ func TestAppState(t *testing.T) { panic(err) } - if bytes.Compare(b1, b2) != 0 { + if !bytes.Equal(b1, b2) { t.Errorf("Bytes are not the same") } } + +func TestAppStateToInvalidState(t *testing.T) { + testAddr := HexToAddress("Mx5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + pubkey := Pubkey{1, 2, 3} + ba := NewBitArray(24) + ba.SetIndex(3, true) + + appState := AppState{} + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState = AppState{ + TotalSlashed: big.NewInt(1e18).String(), + } + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState = AppState{ + TotalSlashed: big.NewInt(1e18).String(), + Validators: []Validator{ + { + TotalBipStake: big.NewInt(1).String(), + PubKey: pubkey, + AccumReward: big.NewInt(1).String(), + AbsentTimes: ba, + }, + { + TotalBipStake: big.NewInt(1).String(), + PubKey: pubkey, + AccumReward: big.NewInt(1).String(), + AbsentTimes: ba, + }, + }, + Candidates: []Candidate{ + { + RewardAddress: testAddr, + OwnerAddress: testAddr, + TotalBipStake: big.NewInt(1).String(), + PubKey: pubkey, + Commission: 1, + Stakes: []Stake{ + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + }, + Status: 1, + }, + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState = AppState{ + TotalSlashed: big.NewInt(1e18).String(), + Validators: []Validator{ + { + TotalBipStake: big.NewInt(1).String(), + PubKey: pubkey, + AccumReward: big.NewInt(1).String(), + AbsentTimes: ba, + }, + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState = AppState{ + TotalSlashed: big.NewInt(1e18).String(), + Validators: []Validator{ + { + TotalBipStake: "", + PubKey: pubkey, + AccumReward: big.NewInt(1).String(), + AbsentTimes: ba, + }, + }, + Candidates: []Candidate{ + { + RewardAddress: testAddr, + OwnerAddress: testAddr, + TotalBipStake: big.NewInt(1).String(), + PubKey: pubkey, + Commission: 1, + Stakes: []Stake{ + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + }, + Status: 1, + }, + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Validators[0].TotalBipStake = big.NewInt(1e18).String() + appState.Validators[0].AccumReward = "" + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Validators[0].AccumReward = big.NewInt(1e18).String() + appState.Validators[0].AbsentTimes = nil + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Accounts = []Account{ + { + Address: testAddr, + Balance: []Balance{ + { + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + }, + }, + Nonce: 1, + }, { + Address: testAddr, + Balance: []Balance{ + { + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + }, + }, + Nonce: 1, + }, + } + + appState.Validators[0].AbsentTimes = ba + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Accounts = []Account{ + { + Address: testAddr, + Balance: []Balance{ + { + Coin: uint64(GetBaseCoinID()), + Value: "", + }, + }, + Nonce: 1, + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Accounts = []Account{ + { + Address: testAddr, + Balance: []Balance{ + { + Coin: uint64(GetBaseCoinID() + 1), + Value: big.NewInt(1).String(), + }, + }, + Nonce: 1, + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Accounts = []Account{ + { + Address: testAddr, + Balance: []Balance{ + { + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + }, + }, + Nonce: 1, + }, + } + + appState.Candidates[0].Stakes = []Stake{ + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Candidates[0].Stakes = []Stake{ + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID() + 1), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + } + + appState.Coins = []Coin{ + { + ID: uint64(GetBaseCoinID() + 2), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Candidates[0].Stakes = []Stake{ + { + Owner: testAddr, + Coin: uint64(GetBaseCoinID() + 1), + Value: big.NewInt(1).String(), + BipValue: big.NewInt(1).String(), + }, + } + + appState.Coins = []Coin{ + { + ID: uint64(GetBaseCoinID() + 1), + Name: "ASD", + Symbol: StrToCoinSymbol("TEST"), + Volume: big.NewInt(1).String(), + Crr: 1, + Reserve: helpers.BipToPip(big.NewInt(100000)).String(), + MaxSupply: helpers.BipToPip(big.NewInt(100000)).String(), + }, + { + ID: uint64(GetBaseCoinID()), + Symbol: GetBaseCoin(), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Coins = []Coin{ + { + ID: uint64(GetBaseCoinID() + 1), + Name: "ASD", + Symbol: StrToCoinSymbol("TEST"), + Volume: big.NewInt(1).String(), + Crr: 1, + Reserve: helpers.BipToPip(big.NewInt(100000)).String(), + MaxSupply: helpers.BipToPip(big.NewInt(100000)).String(), + }, + { + ID: uint64(GetBaseCoinID() + 1), + Name: "ASD", + Symbol: StrToCoinSymbol("TEST"), + Volume: big.NewInt(1).String(), + Crr: 1, + Reserve: helpers.BipToPip(big.NewInt(100000)).String(), + MaxSupply: helpers.BipToPip(big.NewInt(100000)).String(), + }, + { + ID: uint64(GetBaseCoinID()), + Symbol: GetBaseCoin(), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.Coins = []Coin{ + { + ID: uint64(GetBaseCoinID() + 1), + Name: "ASD", + Symbol: StrToCoinSymbol("TEST"), + Volume: big.NewInt(1).String(), + Crr: 1, + Reserve: helpers.BipToPip(big.NewInt(100000)).String(), + MaxSupply: helpers.BipToPip(big.NewInt(100000)).String(), + }, + } + + appState.FrozenFunds = []FrozenFund{ + { + Height: 1, + Address: testAddr, + CandidateKey: &pubkey, + Coin: uint64(GetBaseCoinID() + 1), + Value: big.NewInt(1e18).String(), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.FrozenFunds = []FrozenFund{ + { + Height: 1, + Address: testAddr, + CandidateKey: &pubkey, + Coin: uint64(GetBaseCoinID()), + Value: "", + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.FrozenFunds = []FrozenFund{ + { + Height: 1, + Address: testAddr, + CandidateKey: &pubkey, + Coin: uint64(GetBaseCoinID() + 3), + Value: big.NewInt(1e18).String(), + }, + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + + appState.FrozenFunds = []FrozenFund{ + { + Height: 1, + Address: testAddr, + CandidateKey: &pubkey, + Coin: uint64(GetBaseCoinID()), + Value: big.NewInt(1e18).String(), + }, + } + + appState.UsedChecks = []UsedCheck{ + "00004601d10c33eda76bb16a54a0asddsd8882a57ec34e964aa23e2b5d9aa10957feea", + } + + if appState.Verify() == nil { + t.Error("State is not correct") + } + +} + +func TestHashToString(t *testing.T) { + hash := Hash{5} + if hash.String() != "Mx0500000000000000000000000000000000000000000000000000000000000000" { + t.Error("Hash hex not the same") + } +} + +func TestHashToBytes(t *testing.T) { + b := []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + hash := BytesToHash(b) + + if !bytes.Equal(b, hash.Bytes()) { + t.Error("Bytes are the same") + } +} + +func TestEmptyHash(t *testing.T) { + if EmptyHash(Hash{}) != true { + t.Error("Hash is not empty") + } +} + +func TestHashToSetBytes(t *testing.T) { + b1 := []byte{1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + h := Hash{} + h.SetBytes(b1) + + if !bytes.Equal(b1, h.Bytes()) { + t.Error("Bytes are not the same") + } + + b2 := []byte{2, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + h.SetBytes(b2) + + if !bytes.Equal(b1, h.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestHashToSet(t *testing.T) { + h1, h2 := Hash{5}, Hash{} + h2.Set(h1) + + if !bytes.Equal(h1.Bytes(), h2.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestHashToMarshalText(t *testing.T) { + b := []byte{77, 120, 48, 53, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48} + h := Hash{5} + hashBytes, err := h.MarshalText() + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(hashBytes, b) { + t.Error("Bytes are not the same") + } + + h2 := Hash{} + if err = h2.UnmarshalText(hashBytes); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(h2.Bytes(), h.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestGetVersionFromSymbol(t *testing.T) { + if GetVersionFromSymbol("BIP-5") != 5 { + t.Error("Coin version is incorrect") + } + + if GetVersionFromSymbol("BIP") != 0 { + t.Error("Coin version is incorrect") + } +} + +func TestAddressToSetBytes(t *testing.T) { + b1 := []byte{5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + a := Address{} + a.SetBytes(b1) + if !bytes.Equal(b1, a.Bytes()) { + t.Error("Bytes are not the same") + } + + b2 := []byte{1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + a.SetBytes(b2) + + if !bytes.Equal(b1, a.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestAddressToSet(t *testing.T) { + a1, a2 := Address{5}, Address{} + a2.Set(a1) + + if !bytes.Equal(a1.Bytes(), a2.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestAddressToMarshalText(t *testing.T) { + b := []byte{77, 120, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48} + a := Address{1, 2, 3, 4, 5} + hashBytes, err := a.MarshalText() + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(hashBytes, b) { + t.Error("Bytes are not the same") + } + + a2 := Address{} + if err := a2.UnmarshalText(hashBytes); err != nil { + t.Fatal(err) + } + + if !bytes.Equal(a2.Bytes(), a.Bytes()) { + t.Error("Bytes are not the same") + } +} + +func TestHexToPubkey(t *testing.T) { + p1, p2 := Pubkey{10, 12}, HexToPubkey("Mp0a0c000000000000000000000000000000000000000000000000000000000000") + if !p1.Equals(p2) { + t.Error("Pubkeys are not equal") + } +} + +func TestBytesToPubkey(t *testing.T) { + b := []byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + p := BytesToPubkey(b) + if !bytes.Equal(p.Bytes(), b) { + t.Error("Bytes are not the same") + } +} + +func TestPubkeyToSetBytes(t *testing.T) { + b, p := []byte{1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, Pubkey{} + p.SetBytes(b) + if !bytes.Equal(p.Bytes(), b) { + t.Error("Bytes are not the same") + } + + b2 := []byte{5, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + p.SetBytes(b2) + if !bytes.Equal(p.Bytes(), b) { + t.Error("Bytes are not the same") + } +} + +func TestPubkeyToMarshalText(t *testing.T) { + b := []byte{77, 112, 48, 53, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48} + p := Pubkey{5} + hashBytes, err := p.MarshalText() + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(hashBytes, b) { + t.Error("Bytes are not the same") + } +} diff --git a/core/validators/validators.go b/core/validators/validators.go index 0df7c426b..f7e254646 100644 --- a/core/validators/validators.go +++ b/core/validators/validators.go @@ -1,22 +1,11 @@ package validators -var startHeight uint64 = 0 - +// GetValidatorsCountForBlock returns available validators slots for given height func GetValidatorsCountForBlock(block uint64) int { - block += startHeight - count := 16 + (block/518400)*4 - - if count > 256 { - return 256 - } - - return int(count) + return 64 } +// GetCandidatesCountForBlock returns available candidates slots for given height func GetCandidatesCountForBlock(block uint64) int { - return GetValidatorsCountForBlock(block) * 3 -} - -func SetStartHeight(sHeight uint64) { - startHeight = sHeight + return 192 } diff --git a/core/validators/validators_test.go b/core/validators/validators_test.go index 1e2f8a68a..ef529395c 100644 --- a/core/validators/validators_test.go +++ b/core/validators/validators_test.go @@ -13,19 +13,19 @@ func TestGetValidatorsCountForBlock(t *testing.T) { data := []Results{ { Block: 1, - Result: 16, + Result: 64, }, { Block: 518400 * 2, - Result: 24, + Result: 64, }, { Block: 31104000, - Result: 256, + Result: 64, }, { Block: 31104000 * 2, - Result: 256, + Result: 64, }, } diff --git a/crypto/sha3/LICENSE b/crypto/sha3/LICENSE deleted file mode 100644 index 6a66aea5e..000000000 --- a/crypto/sha3/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/crypto/sha3/PATENTS b/crypto/sha3/PATENTS deleted file mode 100644 index 733099041..000000000 --- a/crypto/sha3/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/crypto/sha3/doc.go b/crypto/sha3/doc.go deleted file mode 100644 index 3dab530f8..000000000 --- a/crypto/sha3/doc.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package sha3 implements the SHA-3 fixed-output-length hash functions and -// the SHAKE variable-output-length hash functions defined by FIPS-202. -// -// Both types of hash function use the "sponge" construction and the Keccak -// permutation. For a detailed specification see http://keccak.noekeon.org/ -// -// -// Guidance -// -// If you aren't sure what function you need, use SHAKE256 with at least 64 -// bytes of output. The SHAKE instances are faster than the SHA3 instances; -// the latter have to allocate memory to conform to the hash.Hash interface. -// -// If you need a secret-key MAC (message authentication code), prepend the -// secret key to the input, hash with SHAKE256 and read at least 32 bytes of -// output. -// -// -// Security strengths -// -// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security -// strength against preimage attacks of x bits. Since they only produce "x" -// bits of output, their collision-resistance is only "x/2" bits. -// -// The SHAKE-256 and -128 functions have a generic security strength of 256 and -// 128 bits against all attacks, provided that at least 2x bits of their output -// is used. Requesting more than 64 or 32 bytes of output, respectively, does -// not increase the collision-resistance of the SHAKE functions. -// -// -// The sponge construction -// -// A sponge builds a pseudo-random function from a public pseudo-random -// permutation, by applying the permutation to a state of "rate + capacity" -// bytes, but hiding "capacity" of the bytes. -// -// A sponge starts out with a zero state. To hash an input using a sponge, up -// to "rate" bytes of the input are XORed into the sponge's state. The sponge -// is then "full" and the permutation is applied to "empty" it. This process is -// repeated until all the input has been "absorbed". The input is then padded. -// The digest is "squeezed" from the sponge in the same way, except that output -// output is copied out instead of input being XORed in. -// -// A sponge is parameterized by its generic security strength, which is equal -// to half its capacity; capacity + rate is equal to the permutation's width. -// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means -// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. -// -// -// Recommendations -// -// The SHAKE functions are recommended for most new uses. They can produce -// output of arbitrary length. SHAKE256, with an output length of at least -// 64 bytes, provides 256-bit security against all attacks. The Keccak team -// recommends it for most applications upgrading from SHA2-512. (NIST chose a -// much stronger, but much slower, sponge instance for SHA3-512.) -// -// The SHA-3 functions are "drop-in" replacements for the SHA-2 functions. -// They produce output of the same length, with the same security strengths -// against all attacks. This means, in particular, that SHA3-256 only has -// 128-bit collision resistance, because its output length is 32 bytes. -package sha3 diff --git a/crypto/sha3/hashes.go b/crypto/sha3/hashes.go deleted file mode 100644 index fa0d7b436..000000000 --- a/crypto/sha3/hashes.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// This file provides functions for creating instances of the SHA-3 -// and SHAKE hash functions, as well as utility functions for hashing -// bytes. - -import ( - "hash" -) - -// NewKeccak256 creates a new Keccak-256 hash. -func NewKeccak256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x01} } - -// NewKeccak512 creates a new Keccak-512 hash. -func NewKeccak512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x01} } - -// New224 creates a new SHA3-224 hash. -// Its generic security strength is 224 bits against preimage attacks, -// and 112 bits against collision attacks. -func New224() hash.Hash { return &state{rate: 144, outputLen: 28, dsbyte: 0x06} } - -// New256 creates a new SHA3-256 hash. -// Its generic security strength is 256 bits against preimage attacks, -// and 128 bits against collision attacks. -func New256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x06} } - -// New384 creates a new SHA3-384 hash. -// Its generic security strength is 384 bits against preimage attacks, -// and 192 bits against collision attacks. -func New384() hash.Hash { return &state{rate: 104, outputLen: 48, dsbyte: 0x06} } - -// New512 creates a new SHA3-512 hash. -// Its generic security strength is 512 bits against preimage attacks, -// and 256 bits against collision attacks. -func New512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x06} } - -// Sum224 returns the SHA3-224 digest of the data. -func Sum224(data []byte) (digest [28]byte) { - h := New224() - h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum256 returns the SHA3-256 digest of the data. -func Sum256(data []byte) (digest [32]byte) { - h := New256() - h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum384 returns the SHA3-384 digest of the data. -func Sum384(data []byte) (digest [48]byte) { - h := New384() - h.Write(data) - h.Sum(digest[:0]) - return -} - -// Sum512 returns the SHA3-512 digest of the data. -func Sum512(data []byte) (digest [64]byte) { - h := New512() - h.Write(data) - h.Sum(digest[:0]) - return -} diff --git a/crypto/sha3/keccakf.go b/crypto/sha3/keccakf.go deleted file mode 100644 index 46d03ed38..000000000 --- a/crypto/sha3/keccakf.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !amd64 appengine gccgo - -package sha3 - -// rc stores the round constants for use in the ι step. -var rc = [24]uint64{ - 0x0000000000000001, - 0x0000000000008082, - 0x800000000000808A, - 0x8000000080008000, - 0x000000000000808B, - 0x0000000080000001, - 0x8000000080008081, - 0x8000000000008009, - 0x000000000000008A, - 0x0000000000000088, - 0x0000000080008009, - 0x000000008000000A, - 0x000000008000808B, - 0x800000000000008B, - 0x8000000000008089, - 0x8000000000008003, - 0x8000000000008002, - 0x8000000000000080, - 0x000000000000800A, - 0x800000008000000A, - 0x8000000080008081, - 0x8000000000008080, - 0x0000000080000001, - 0x8000000080008008, -} - -// keccakF1600 applies the Keccak permutation to a 1600b-wide -// state represented as a slice of 25 uint64s. -func keccakF1600(a *[25]uint64) { - // Implementation translated from Keccak-inplace.c - // in the keccak reference code. - var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 - - for i := 0; i < 24; i += 4 { - // Combines the 5 steps in each round into 2 steps. - // Unrolls 4 rounds per loop and spreads some steps across rounds. - - // Round 1 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[6] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[12] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[18] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[24] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] - a[6] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[16] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[22] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[3] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[10] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[1] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[7] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[19] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[20] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[11] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[23] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[4] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[5] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[2] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[8] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[14] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[15] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - // Round 2 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[16] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[7] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[23] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[14] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] - a[16] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[11] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[2] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[18] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[20] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[6] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[22] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[4] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[15] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[1] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[8] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[24] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[10] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[12] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[3] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[19] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[5] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - // Round 3 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[11] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[22] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[8] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[19] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] - a[11] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[1] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[12] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[23] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[15] = bc0 ^ (bc2 &^ bc1) - a[1] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[16] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[2] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[24] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[5] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[6] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[3] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[14] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[20] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[7] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[18] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[4] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[10] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - // Round 4 - bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] - bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] - bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] - bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] - bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] - d0 = bc4 ^ (bc1<<1 | bc1>>63) - d1 = bc0 ^ (bc2<<1 | bc2>>63) - d2 = bc1 ^ (bc3<<1 | bc3>>63) - d3 = bc2 ^ (bc4<<1 | bc4>>63) - d4 = bc3 ^ (bc0<<1 | bc0>>63) - - bc0 = a[0] ^ d0 - t = a[1] ^ d1 - bc1 = t<<44 | t>>(64-44) - t = a[2] ^ d2 - bc2 = t<<43 | t>>(64-43) - t = a[3] ^ d3 - bc3 = t<<21 | t>>(64-21) - t = a[4] ^ d4 - bc4 = t<<14 | t>>(64-14) - a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] - a[1] = bc1 ^ (bc3 &^ bc2) - a[2] = bc2 ^ (bc4 &^ bc3) - a[3] = bc3 ^ (bc0 &^ bc4) - a[4] = bc4 ^ (bc1 &^ bc0) - - t = a[5] ^ d0 - bc2 = t<<3 | t>>(64-3) - t = a[6] ^ d1 - bc3 = t<<45 | t>>(64-45) - t = a[7] ^ d2 - bc4 = t<<61 | t>>(64-61) - t = a[8] ^ d3 - bc0 = t<<28 | t>>(64-28) - t = a[9] ^ d4 - bc1 = t<<20 | t>>(64-20) - a[5] = bc0 ^ (bc2 &^ bc1) - a[6] = bc1 ^ (bc3 &^ bc2) - a[7] = bc2 ^ (bc4 &^ bc3) - a[8] = bc3 ^ (bc0 &^ bc4) - a[9] = bc4 ^ (bc1 &^ bc0) - - t = a[10] ^ d0 - bc4 = t<<18 | t>>(64-18) - t = a[11] ^ d1 - bc0 = t<<1 | t>>(64-1) - t = a[12] ^ d2 - bc1 = t<<6 | t>>(64-6) - t = a[13] ^ d3 - bc2 = t<<25 | t>>(64-25) - t = a[14] ^ d4 - bc3 = t<<8 | t>>(64-8) - a[10] = bc0 ^ (bc2 &^ bc1) - a[11] = bc1 ^ (bc3 &^ bc2) - a[12] = bc2 ^ (bc4 &^ bc3) - a[13] = bc3 ^ (bc0 &^ bc4) - a[14] = bc4 ^ (bc1 &^ bc0) - - t = a[15] ^ d0 - bc1 = t<<36 | t>>(64-36) - t = a[16] ^ d1 - bc2 = t<<10 | t>>(64-10) - t = a[17] ^ d2 - bc3 = t<<15 | t>>(64-15) - t = a[18] ^ d3 - bc4 = t<<56 | t>>(64-56) - t = a[19] ^ d4 - bc0 = t<<27 | t>>(64-27) - a[15] = bc0 ^ (bc2 &^ bc1) - a[16] = bc1 ^ (bc3 &^ bc2) - a[17] = bc2 ^ (bc4 &^ bc3) - a[18] = bc3 ^ (bc0 &^ bc4) - a[19] = bc4 ^ (bc1 &^ bc0) - - t = a[20] ^ d0 - bc3 = t<<41 | t>>(64-41) - t = a[21] ^ d1 - bc4 = t<<2 | t>>(64-2) - t = a[22] ^ d2 - bc0 = t<<62 | t>>(64-62) - t = a[23] ^ d3 - bc1 = t<<55 | t>>(64-55) - t = a[24] ^ d4 - bc2 = t<<39 | t>>(64-39) - a[20] = bc0 ^ (bc2 &^ bc1) - a[21] = bc1 ^ (bc3 &^ bc2) - a[22] = bc2 ^ (bc4 &^ bc3) - a[23] = bc3 ^ (bc0 &^ bc4) - a[24] = bc4 ^ (bc1 &^ bc0) - } -} diff --git a/crypto/sha3/keccakf_amd64.go b/crypto/sha3/keccakf_amd64.go deleted file mode 100644 index de035c550..000000000 --- a/crypto/sha3/keccakf_amd64.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build amd64,!appengine,!gccgo - -package sha3 - -// This function is implemented in keccakf_amd64.s. - -//go:noescape - -func keccakF1600(state *[25]uint64) diff --git a/crypto/sha3/keccakf_amd64.s b/crypto/sha3/keccakf_amd64.s deleted file mode 100644 index f88533acc..000000000 --- a/crypto/sha3/keccakf_amd64.s +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build amd64,!appengine,!gccgo - -// This code was translated into a form compatible with 6a from the public -// domain sources at https://github.com/gvanas/KeccakCodePackage - -// Offsets in state -#define _ba (0*8) -#define _be (1*8) -#define _bi (2*8) -#define _bo (3*8) -#define _bu (4*8) -#define _ga (5*8) -#define _ge (6*8) -#define _gi (7*8) -#define _go (8*8) -#define _gu (9*8) -#define _ka (10*8) -#define _ke (11*8) -#define _ki (12*8) -#define _ko (13*8) -#define _ku (14*8) -#define _ma (15*8) -#define _me (16*8) -#define _mi (17*8) -#define _mo (18*8) -#define _mu (19*8) -#define _sa (20*8) -#define _se (21*8) -#define _si (22*8) -#define _so (23*8) -#define _su (24*8) - -// Temporary registers -#define rT1 AX - -// Round vars -#define rpState DI -#define rpStack SP - -#define rDa BX -#define rDe CX -#define rDi DX -#define rDo R8 -#define rDu R9 - -#define rBa R10 -#define rBe R11 -#define rBi R12 -#define rBo R13 -#define rBu R14 - -#define rCa SI -#define rCe BP -#define rCi rBi -#define rCo rBo -#define rCu R15 - -#define MOVQ_RBI_RCE MOVQ rBi, rCe -#define XORQ_RT1_RCA XORQ rT1, rCa -#define XORQ_RT1_RCE XORQ rT1, rCe -#define XORQ_RBA_RCU XORQ rBa, rCu -#define XORQ_RBE_RCU XORQ rBe, rCu -#define XORQ_RDU_RCU XORQ rDu, rCu -#define XORQ_RDA_RCA XORQ rDa, rCa -#define XORQ_RDE_RCE XORQ rDe, rCe - -#define mKeccakRound(iState, oState, rc, B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, S_RDE_RCE) \ - /* Prepare round */ \ - MOVQ rCe, rDa; \ - ROLQ $1, rDa; \ - \ - MOVQ _bi(iState), rCi; \ - XORQ _gi(iState), rDi; \ - XORQ rCu, rDa; \ - XORQ _ki(iState), rCi; \ - XORQ _mi(iState), rDi; \ - XORQ rDi, rCi; \ - \ - MOVQ rCi, rDe; \ - ROLQ $1, rDe; \ - \ - MOVQ _bo(iState), rCo; \ - XORQ _go(iState), rDo; \ - XORQ rCa, rDe; \ - XORQ _ko(iState), rCo; \ - XORQ _mo(iState), rDo; \ - XORQ rDo, rCo; \ - \ - MOVQ rCo, rDi; \ - ROLQ $1, rDi; \ - \ - MOVQ rCu, rDo; \ - XORQ rCe, rDi; \ - ROLQ $1, rDo; \ - \ - MOVQ rCa, rDu; \ - XORQ rCi, rDo; \ - ROLQ $1, rDu; \ - \ - /* Result b */ \ - MOVQ _ba(iState), rBa; \ - MOVQ _ge(iState), rBe; \ - XORQ rCo, rDu; \ - MOVQ _ki(iState), rBi; \ - MOVQ _mo(iState), rBo; \ - MOVQ _su(iState), rBu; \ - XORQ rDe, rBe; \ - ROLQ $44, rBe; \ - XORQ rDi, rBi; \ - XORQ rDa, rBa; \ - ROLQ $43, rBi; \ - \ - MOVQ rBe, rCa; \ - MOVQ rc, rT1; \ - ORQ rBi, rCa; \ - XORQ rBa, rT1; \ - XORQ rT1, rCa; \ - MOVQ rCa, _ba(oState); \ - \ - XORQ rDu, rBu; \ - ROLQ $14, rBu; \ - MOVQ rBa, rCu; \ - ANDQ rBe, rCu; \ - XORQ rBu, rCu; \ - MOVQ rCu, _bu(oState); \ - \ - XORQ rDo, rBo; \ - ROLQ $21, rBo; \ - MOVQ rBo, rT1; \ - ANDQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _bi(oState); \ - \ - NOTQ rBi; \ - ORQ rBa, rBu; \ - ORQ rBo, rBi; \ - XORQ rBo, rBu; \ - XORQ rBe, rBi; \ - MOVQ rBu, _bo(oState); \ - MOVQ rBi, _be(oState); \ - B_RBI_RCE; \ - \ - /* Result g */ \ - MOVQ _gu(iState), rBe; \ - XORQ rDu, rBe; \ - MOVQ _ka(iState), rBi; \ - ROLQ $20, rBe; \ - XORQ rDa, rBi; \ - ROLQ $3, rBi; \ - MOVQ _bo(iState), rBa; \ - MOVQ rBe, rT1; \ - ORQ rBi, rT1; \ - XORQ rDo, rBa; \ - MOVQ _me(iState), rBo; \ - MOVQ _si(iState), rBu; \ - ROLQ $28, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ga(oState); \ - G_RT1_RCA; \ - \ - XORQ rDe, rBo; \ - ROLQ $45, rBo; \ - MOVQ rBi, rT1; \ - ANDQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _ge(oState); \ - G_RT1_RCE; \ - \ - XORQ rDi, rBu; \ - ROLQ $61, rBu; \ - MOVQ rBu, rT1; \ - ORQ rBa, rT1; \ - XORQ rBo, rT1; \ - MOVQ rT1, _go(oState); \ - \ - ANDQ rBe, rBa; \ - XORQ rBu, rBa; \ - MOVQ rBa, _gu(oState); \ - NOTQ rBu; \ - G_RBA_RCU; \ - \ - ORQ rBu, rBo; \ - XORQ rBi, rBo; \ - MOVQ rBo, _gi(oState); \ - \ - /* Result k */ \ - MOVQ _be(iState), rBa; \ - MOVQ _gi(iState), rBe; \ - MOVQ _ko(iState), rBi; \ - MOVQ _mu(iState), rBo; \ - MOVQ _sa(iState), rBu; \ - XORQ rDi, rBe; \ - ROLQ $6, rBe; \ - XORQ rDo, rBi; \ - ROLQ $25, rBi; \ - MOVQ rBe, rT1; \ - ORQ rBi, rT1; \ - XORQ rDe, rBa; \ - ROLQ $1, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ka(oState); \ - K_RT1_RCA; \ - \ - XORQ rDu, rBo; \ - ROLQ $8, rBo; \ - MOVQ rBi, rT1; \ - ANDQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _ke(oState); \ - K_RT1_RCE; \ - \ - XORQ rDa, rBu; \ - ROLQ $18, rBu; \ - NOTQ rBo; \ - MOVQ rBo, rT1; \ - ANDQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _ki(oState); \ - \ - MOVQ rBu, rT1; \ - ORQ rBa, rT1; \ - XORQ rBo, rT1; \ - MOVQ rT1, _ko(oState); \ - \ - ANDQ rBe, rBa; \ - XORQ rBu, rBa; \ - MOVQ rBa, _ku(oState); \ - K_RBA_RCU; \ - \ - /* Result m */ \ - MOVQ _ga(iState), rBe; \ - XORQ rDa, rBe; \ - MOVQ _ke(iState), rBi; \ - ROLQ $36, rBe; \ - XORQ rDe, rBi; \ - MOVQ _bu(iState), rBa; \ - ROLQ $10, rBi; \ - MOVQ rBe, rT1; \ - MOVQ _mi(iState), rBo; \ - ANDQ rBi, rT1; \ - XORQ rDu, rBa; \ - MOVQ _so(iState), rBu; \ - ROLQ $27, rBa; \ - XORQ rBa, rT1; \ - MOVQ rT1, _ma(oState); \ - M_RT1_RCA; \ - \ - XORQ rDi, rBo; \ - ROLQ $15, rBo; \ - MOVQ rBi, rT1; \ - ORQ rBo, rT1; \ - XORQ rBe, rT1; \ - MOVQ rT1, _me(oState); \ - M_RT1_RCE; \ - \ - XORQ rDo, rBu; \ - ROLQ $56, rBu; \ - NOTQ rBo; \ - MOVQ rBo, rT1; \ - ORQ rBu, rT1; \ - XORQ rBi, rT1; \ - MOVQ rT1, _mi(oState); \ - \ - ORQ rBa, rBe; \ - XORQ rBu, rBe; \ - MOVQ rBe, _mu(oState); \ - \ - ANDQ rBa, rBu; \ - XORQ rBo, rBu; \ - MOVQ rBu, _mo(oState); \ - M_RBE_RCU; \ - \ - /* Result s */ \ - MOVQ _bi(iState), rBa; \ - MOVQ _go(iState), rBe; \ - MOVQ _ku(iState), rBi; \ - XORQ rDi, rBa; \ - MOVQ _ma(iState), rBo; \ - ROLQ $62, rBa; \ - XORQ rDo, rBe; \ - MOVQ _se(iState), rBu; \ - ROLQ $55, rBe; \ - \ - XORQ rDu, rBi; \ - MOVQ rBa, rDu; \ - XORQ rDe, rBu; \ - ROLQ $2, rBu; \ - ANDQ rBe, rDu; \ - XORQ rBu, rDu; \ - MOVQ rDu, _su(oState); \ - \ - ROLQ $39, rBi; \ - S_RDU_RCU; \ - NOTQ rBe; \ - XORQ rDa, rBo; \ - MOVQ rBe, rDa; \ - ANDQ rBi, rDa; \ - XORQ rBa, rDa; \ - MOVQ rDa, _sa(oState); \ - S_RDA_RCA; \ - \ - ROLQ $41, rBo; \ - MOVQ rBi, rDe; \ - ORQ rBo, rDe; \ - XORQ rBe, rDe; \ - MOVQ rDe, _se(oState); \ - S_RDE_RCE; \ - \ - MOVQ rBo, rDi; \ - MOVQ rBu, rDo; \ - ANDQ rBu, rDi; \ - ORQ rBa, rDo; \ - XORQ rBi, rDi; \ - XORQ rBo, rDo; \ - MOVQ rDi, _si(oState); \ - MOVQ rDo, _so(oState) \ - -// func keccakF1600(state *[25]uint64) -TEXT ·keccakF1600(SB), 0, $200-8 - MOVQ state+0(FP), rpState - - // Convert the user state into an internal state - NOTQ _be(rpState) - NOTQ _bi(rpState) - NOTQ _go(rpState) - NOTQ _ki(rpState) - NOTQ _mi(rpState) - NOTQ _sa(rpState) - - // Execute the KeccakF permutation - MOVQ _ba(rpState), rCa - MOVQ _be(rpState), rCe - MOVQ _bu(rpState), rCu - - XORQ _ga(rpState), rCa - XORQ _ge(rpState), rCe - XORQ _gu(rpState), rCu - - XORQ _ka(rpState), rCa - XORQ _ke(rpState), rCe - XORQ _ku(rpState), rCu - - XORQ _ma(rpState), rCa - XORQ _me(rpState), rCe - XORQ _mu(rpState), rCu - - XORQ _sa(rpState), rCa - XORQ _se(rpState), rCe - MOVQ _si(rpState), rDi - MOVQ _so(rpState), rDo - XORQ _su(rpState), rCu - - mKeccakRound(rpState, rpStack, $0x0000000000000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000000008082, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x800000000000808a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000080008000, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000008a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x0000000000000088, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x0000000080008009, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x000000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000008000808b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x800000000000008b, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000000008089, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008003, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000000008002, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000000080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x000000000000800a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x800000008000000a, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x8000000080008081, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000000008080, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpState, rpStack, $0x0000000080000001, MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) - mKeccakRound(rpStack, rpState, $0x8000000080008008, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP) - - // Revert the internal state to the user state - NOTQ _be(rpState) - NOTQ _bi(rpState) - NOTQ _go(rpState) - NOTQ _ki(rpState) - NOTQ _mi(rpState) - NOTQ _sa(rpState) - - RET diff --git a/crypto/sha3/register.go b/crypto/sha3/register.go deleted file mode 100644 index 3cf6a22e0..000000000 --- a/crypto/sha3/register.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build go1.4 - -package sha3 - -import ( - "crypto" -) - -func init() { - crypto.RegisterHash(crypto.SHA3_224, New224) - crypto.RegisterHash(crypto.SHA3_256, New256) - crypto.RegisterHash(crypto.SHA3_384, New384) - crypto.RegisterHash(crypto.SHA3_512, New512) -} diff --git a/crypto/sha3/sha3.go b/crypto/sha3/sha3.go deleted file mode 100644 index b12a35c87..000000000 --- a/crypto/sha3/sha3.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// spongeDirection indicates the direction bytes are flowing through the sponge. -type spongeDirection int - -const ( - // spongeAbsorbing indicates that the sponge is absorbing input. - spongeAbsorbing spongeDirection = iota - // spongeSqueezing indicates that the sponge is being squeezed. - spongeSqueezing -) - -const ( - // maxRate is the maximum size of the internal buffer. SHAKE-256 - // currently needs the largest buffer. - maxRate = 168 -) - -type state struct { - // Generic sponge components. - a [25]uint64 // main state of the hash - buf []byte // points into storage - rate int // the number of bytes of state to use - - // dsbyte contains the "domain separation" bits and the first bit of - // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the - // SHA-3 and SHAKE functions by appending bitstrings to the message. - // Using a little-endian bit-ordering convention, these are "01" for SHA-3 - // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the - // padding rule from section 5.1 is applied to pad the message to a multiple - // of the rate, which involves adding a "1" bit, zero or more "0" bits, and - // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, - // giving 00000110b (0x06) and 00011111b (0x1f). - // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf - // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and - // Extendable-Output Functions (May 2014)" - dsbyte byte - storage [maxRate]byte - - // Specific to SHA-3 and SHAKE. - outputLen int // the default output size in bytes - state spongeDirection // whether the sponge is absorbing or squeezing -} - -// BlockSize returns the rate of sponge underlying this hash function. -func (d *state) BlockSize() int { return d.rate } - -// Size returns the output size of the hash function in bytes. -func (d *state) Size() int { return d.outputLen } - -// Reset clears the internal state by zeroing the sponge state and -// the byte buffer, and setting Sponge.state to absorbing. -func (d *state) Reset() { - // Zero the permutation's state. - for i := range d.a { - d.a[i] = 0 - } - d.state = spongeAbsorbing - d.buf = d.storage[:0] -} - -func (d *state) clone() *state { - ret := *d - if ret.state == spongeAbsorbing { - ret.buf = ret.storage[:len(ret.buf)] - } else { - ret.buf = ret.storage[d.rate-cap(d.buf) : d.rate] - } - - return &ret -} - -// permute applies the KeccakF-1600 permutation. It handles -// any input-output buffering. -func (d *state) permute() { - switch d.state { - case spongeAbsorbing: - // If we're absorbing, we need to xor the input into the state - // before applying the permutation. - xorIn(d, d.buf) - d.buf = d.storage[:0] - keccakF1600(&d.a) - case spongeSqueezing: - // If we're squeezing, we need to apply the permutatin before - // copying more output. - keccakF1600(&d.a) - d.buf = d.storage[:d.rate] - copyOut(d, d.buf) - } -} - -// pads appends the domain separation bits in dsbyte, applies -// the multi-bitrate 10..1 padding rule, and permutes the state. -func (d *state) padAndPermute(dsbyte byte) { - if d.buf == nil { - d.buf = d.storage[:0] - } - // Pad with this instance's domain-separator bits. We know that there's - // at least one byte of space in d.buf because, if it were full, - // permute would have been called to empty it. dsbyte also contains the - // first one bit for the padding. See the comment in the state struct. - d.buf = append(d.buf, dsbyte) - zerosStart := len(d.buf) - d.buf = d.storage[:d.rate] - for i := zerosStart; i < d.rate; i++ { - d.buf[i] = 0 - } - // This adds the final one bit for the padding. Because of the way that - // bits are numbered from the LSB upwards, the final bit is the MSB of - // the last byte. - d.buf[d.rate-1] ^= 0x80 - // Apply the permutation - d.permute() - d.state = spongeSqueezing - d.buf = d.storage[:d.rate] - copyOut(d, d.buf) -} - -// Write absorbs more data into the hash's state. It produces an error -// if more data is written to the ShakeHash after writing -func (d *state) Write(p []byte) (written int, err error) { - if d.state != spongeAbsorbing { - panic("sha3: write to sponge after read") - } - if d.buf == nil { - d.buf = d.storage[:0] - } - written = len(p) - - for len(p) > 0 { - if len(d.buf) == 0 && len(p) >= d.rate { - // The fast path; absorb a full "rate" bytes of input and apply the permutation. - xorIn(d, p[:d.rate]) - p = p[d.rate:] - keccakF1600(&d.a) - } else { - // The slow path; buffer the input until we can fill the sponge, and then xor it in. - todo := d.rate - len(d.buf) - if todo > len(p) { - todo = len(p) - } - d.buf = append(d.buf, p[:todo]...) - p = p[todo:] - - // If the sponge is full, apply the permutation. - if len(d.buf) == d.rate { - d.permute() - } - } - } - - return -} - -// Read squeezes an arbitrary number of bytes from the sponge. -func (d *state) Read(out []byte) (n int, err error) { - // If we're still absorbing, pad and apply the permutation. - if d.state == spongeAbsorbing { - d.padAndPermute(d.dsbyte) - } - - n = len(out) - - // Now, do the squeezing. - for len(out) > 0 { - n := copy(out, d.buf) - d.buf = d.buf[n:] - out = out[n:] - - // Apply the permutation if we've squeezed the sponge dry. - if len(d.buf) == 0 { - d.permute() - } - } - - return -} - -// Sum applies padding to the hash state and then squeezes out the desired -// number of output bytes. -func (d *state) Sum(in []byte) []byte { - // Make a copy of the original hash so that caller can keep writing - // and summing. - dup := d.clone() - hash := make([]byte, dup.outputLen) - dup.Read(hash) - return append(in, hash...) -} diff --git a/crypto/sha3/shake.go b/crypto/sha3/shake.go deleted file mode 100644 index 841f9860f..000000000 --- a/crypto/sha3/shake.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sha3 - -// This file defines the ShakeHash interface, and provides -// functions for creating SHAKE instances, as well as utility -// functions for hashing bytes to arbitrary-length output. - -import ( - "io" -) - -// ShakeHash defines the interface to hash functions that -// support arbitrary-length output. -type ShakeHash interface { - // Write absorbs more data into the hash's state. It panics if input is - // written to it after output has been read from it. - io.Writer - - // Read reads more output from the hash; reading affects the hash's - // state. (ShakeHash.Read is thus very different from Hash.Sum) - // It never returns an error. - io.Reader - - // Clone returns a copy of the ShakeHash in its current state. - Clone() ShakeHash - - // Reset resets the ShakeHash to its initial state. - Reset() -} - -func (d *state) Clone() ShakeHash { - return d.clone() -} - -// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash. -// Its generic security strength is 128 bits against all attacks if at -// least 32 bytes of its output are used. -func NewShake128() ShakeHash { return &state{rate: 168, dsbyte: 0x1f} } - -// NewShake256 creates a new SHAKE128 variable-output-length ShakeHash. -// Its generic security strength is 256 bits against all attacks if -// at least 64 bytes of its output are used. -func NewShake256() ShakeHash { return &state{rate: 136, dsbyte: 0x1f} } - -// ShakeSum128 writes an arbitrary-length digest of data into hash. -func ShakeSum128(hash, data []byte) { - h := NewShake128() - h.Write(data) - h.Read(hash) -} - -// ShakeSum256 writes an arbitrary-length digest of data into hash. -func ShakeSum256(hash, data []byte) { - h := NewShake256() - h.Write(data) - h.Read(hash) -} diff --git a/crypto/sha3/xor.go b/crypto/sha3/xor.go deleted file mode 100644 index 42137426b..000000000 --- a/crypto/sha3/xor.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !amd64,!386,!ppc64le appengine - -package sha3 - -var ( - xorIn = xorInGeneric - copyOut = copyOutGeneric - xorInUnaligned = xorInGeneric - copyOutUnaligned = copyOutGeneric -) diff --git a/crypto/sha3/xor_unaligned.go b/crypto/sha3/xor_unaligned.go deleted file mode 100644 index 6b7a6628c..000000000 --- a/crypto/sha3/xor_unaligned.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build amd64 386 ppc64le -// +build !appengine - -package sha3 - -import "unsafe" - -func xorInUnaligned(d *state, buf []byte) { - bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0])) - n := len(buf) - if n >= 72 { - d.a[0] ^= bw[0] - d.a[1] ^= bw[1] - d.a[2] ^= bw[2] - d.a[3] ^= bw[3] - d.a[4] ^= bw[4] - d.a[5] ^= bw[5] - d.a[6] ^= bw[6] - d.a[7] ^= bw[7] - d.a[8] ^= bw[8] - } - if n >= 104 { - d.a[9] ^= bw[9] - d.a[10] ^= bw[10] - d.a[11] ^= bw[11] - d.a[12] ^= bw[12] - } - if n >= 136 { - d.a[13] ^= bw[13] - d.a[14] ^= bw[14] - d.a[15] ^= bw[15] - d.a[16] ^= bw[16] - } - if n >= 144 { - d.a[17] ^= bw[17] - } - if n >= 168 { - d.a[18] ^= bw[18] - d.a[19] ^= bw[19] - d.a[20] ^= bw[20] - } -} - -func copyOutUnaligned(d *state, buf []byte) { - ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) - copy(buf, ab[:]) -} - -var ( - xorIn = xorInUnaligned - copyOut = copyOutUnaligned -) diff --git a/docker-compose.yml b/docker-compose.yml index 54c056882..20853b00e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: image: minterteam/minter:latest #build: . volumes: - - minter_data:/minter/data + - minter_data:/minter/.minter ports: - "8841:8841" - "127.0.0.1:3000:3000" diff --git a/formula/formula.go b/formula/formula.go index 404ea0251..bd8769a72 100644 --- a/formula/formula.go +++ b/formula/formula.go @@ -10,12 +10,9 @@ const ( precision = 100 ) -func newFloat(x float64) *big.Float { - return big.NewFloat(x).SetPrec(precision) -} - +// CalculatePurchaseReturn calculates amount of coin that user will receive by depositing given amount of BIP // Return = supply * ((1 + deposit / reserve) ^ (crr / 100) - 1) -func CalculatePurchaseReturn(supply *big.Int, reserve *big.Int, crr uint, deposit *big.Int) *big.Int { +func CalculatePurchaseReturn(supply *big.Int, reserve *big.Int, crr uint32, deposit *big.Int) *big.Int { if deposit.Cmp(types.Big0) == 0 { return big.NewInt(0) } @@ -41,9 +38,9 @@ func CalculatePurchaseReturn(supply *big.Int, reserve *big.Int, crr uint, deposi return result } -// reversed function CalculatePurchaseReturn -// deposit = reserve * (((wantReceive + supply) / supply)^(100/c) - 1) -func CalculatePurchaseAmount(supply *big.Int, reserve *big.Int, crr uint, wantReceive *big.Int) *big.Int { +// CalculatePurchaseAmount is the reversed version of function CalculatePurchaseReturn +// Deposit = reserve * (((wantReceive + supply) / supply)^(100/c) - 1) +func CalculatePurchaseAmount(supply *big.Int, reserve *big.Int, crr uint32, wantReceive *big.Int) *big.Int { if wantReceive.Cmp(types.Big0) == 0 { return big.NewInt(0) } @@ -69,8 +66,9 @@ func CalculatePurchaseAmount(supply *big.Int, reserve *big.Int, crr uint, wantRe return result } +// CalculateSaleReturn returns amount of BIP user will receive by depositing given amount of coins // Return = reserve * (1 - (1 - sellAmount / supply) ^ (100 / crr)) -func CalculateSaleReturn(supply *big.Int, reserve *big.Int, crr uint, sellAmount *big.Int) *big.Int { +func CalculateSaleReturn(supply *big.Int, reserve *big.Int, crr uint32, sellAmount *big.Int) *big.Int { // special case for 0 sell amount if sellAmount.Cmp(types.Big0) == 0 { return big.NewInt(0) @@ -103,9 +101,9 @@ func CalculateSaleReturn(supply *big.Int, reserve *big.Int, crr uint, sellAmount return result } -// reversed function CalculateSaleReturn -// -(-1 + (-(wantReceive - reserve)/reserve)^(1/crr)) * supply -func CalculateSaleAmount(supply *big.Int, reserve *big.Int, crr uint, wantReceive *big.Int) *big.Int { +// CalculateSaleAmount is the reversed version of function CalculateSaleReturn +// Deposit = -(-1 + (-(wantReceive - reserve)/reserve)^(1/crr)) * supply +func CalculateSaleAmount(supply *big.Int, reserve *big.Int, crr uint32, wantReceive *big.Int) *big.Int { if wantReceive.Cmp(types.Big0) == 0 { return big.NewInt(0) } @@ -133,3 +131,7 @@ func CalculateSaleAmount(supply *big.Int, reserve *big.Int, crr uint, wantReceiv return result } + +func newFloat(x float64) *big.Float { + return big.NewFloat(x).SetPrec(precision) +} diff --git a/formula/formula_test.go b/formula/formula_test.go index 50f054aa7..f56f4aaf6 100644 --- a/formula/formula_test.go +++ b/formula/formula_test.go @@ -8,13 +8,12 @@ import ( type PurchaseReturnData struct { Supply *big.Int Reserve *big.Int - Crr uint + Crr uint32 Deposit *big.Int Result *big.Int } func TestCalculatePurchaseReturn(t *testing.T) { - data := []PurchaseReturnData{ { Supply: big.NewInt(1000000), @@ -30,6 +29,13 @@ func TestCalculatePurchaseReturn(t *testing.T) { Deposit: big.NewInt(100), Result: big.NewInt(1000000), }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(100), + Crr: 100, + Deposit: big.NewInt(0), + Result: big.NewInt(0), + }, } for _, item := range data { @@ -41,16 +47,57 @@ func TestCalculatePurchaseReturn(t *testing.T) { } } +type PurchaseAmountData struct { + Supply *big.Int + Reserve *big.Int + Crr uint32 + WantReceive *big.Int + Deposit *big.Int +} + +func TestCalculatePurchaseAmount(t *testing.T) { + data := []PurchaseAmountData{ + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 40, + WantReceive: big.NewInt(100), + Deposit: big.NewInt(250), + }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 100, + WantReceive: big.NewInt(100), + Deposit: big.NewInt(100), + }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 100, + WantReceive: big.NewInt(0), + Deposit: big.NewInt(0), + }, + } + + for _, item := range data { + deposit := CalculatePurchaseAmount(item.Supply, item.Reserve, item.Crr, item.WantReceive) + + if deposit.Cmp(item.Deposit) != 0 { + t.Errorf("CalculatePurchaseAmount Deposit is not correct. Expected %s, got %s", item.Deposit, deposit) + } + } +} + type CalculateSaleReturnData struct { Supply *big.Int Reserve *big.Int - Crr uint + Crr uint32 SellAmount *big.Int Result *big.Int } func TestCalculateSaleReturn(t *testing.T) { - data := []CalculateSaleReturnData{ { Supply: big.NewInt(1000000), @@ -66,6 +113,20 @@ func TestCalculateSaleReturn(t *testing.T) { SellAmount: big.NewInt(100000), Result: big.NewInt(65), }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(100), + Crr: 10, + SellAmount: big.NewInt(0), + Result: big.NewInt(0), + }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 100, + SellAmount: big.NewInt(100), + Result: big.NewInt(100), + }, } for _, item := range data { @@ -80,13 +141,12 @@ func TestCalculateSaleReturn(t *testing.T) { type CalculateBuyDepositData struct { Supply *big.Int Reserve *big.Int - Crr uint + Crr uint32 WantReceive *big.Int Result *big.Int } func TestCalculateBuyDeposit(t *testing.T) { - data := []CalculateBuyDepositData{ { Supply: big.NewInt(1000000), @@ -102,6 +162,20 @@ func TestCalculateBuyDeposit(t *testing.T) { WantReceive: big.NewInt(100), Result: big.NewInt(1000000), }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 100, + WantReceive: big.NewInt(100), + Result: big.NewInt(100), + }, + { + Supply: big.NewInt(1000000), + Reserve: big.NewInt(1000000), + Crr: 100, + WantReceive: big.NewInt(0), + Result: big.NewInt(0), + }, } for _, item := range data { diff --git a/go.mod b/go.mod index ad4929638..ce035a0c6 100644 --- a/go.mod +++ b/go.mod @@ -1,36 +1,42 @@ module github.com/MinterTeam/minter-go-node -go 1.13 +go 1.15 require ( - github.com/MinterTeam/events-db v0.1.6 - github.com/MinterTeam/node-grpc-gateway v1.0.0 - github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d + github.com/MinterTeam/node-grpc-gateway v1.2.1-0.20201019072706-326f69c6e526 + github.com/btcsuite/btcd v0.20.1-beta github.com/c-bata/go-prompt v0.2.3 github.com/go-kit/kit v0.10.0 - github.com/golang/protobuf v1.3.4 + github.com/golang/protobuf v1.4.2 github.com/google/uuid v1.1.1 - github.com/gorilla/websocket v1.4.1 + github.com/gorilla/handlers v1.4.2 + github.com/gorilla/websocket v1.4.2 + github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/grpc-ecosystem/grpc-gateway v1.12.0 + github.com/grpc-ecosystem/grpc-gateway v1.15.0 github.com/marcusolsson/tui-go v0.4.0 + github.com/mattn/go-tty v0.0.3 // indirect github.com/pkg/errors v0.9.1 + github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 // indirect github.com/prometheus/client_golang v1.5.1 + github.com/rakyll/statik v0.1.7 github.com/rs/cors v1.7.0 - github.com/spf13/cobra v0.0.6 - github.com/spf13/viper v1.6.2 - github.com/stretchr/testify v1.5.1 + github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.6.3 + github.com/stretchr/testify v1.6.1 github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d github.com/tendermint/go-amino v0.15.1 - github.com/tendermint/iavl v0.13.2 - github.com/tendermint/tendermint v0.33.3 - github.com/tendermint/tm-db v0.5.0 - github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 + github.com/tendermint/iavl v0.14.0 + github.com/tendermint/tendermint v0.33.8 + github.com/tendermint/tm-db v0.5.1 + github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 github.com/urfave/cli/v2 v2.0.0 - golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 - golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 - google.golang.org/grpc v1.28.0 - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 + golang.org/x/sys v0.0.0-20200523222454-059865788121 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/grpc v1.32.0 + google.golang.org/protobuf v1.25.0 + gopkg.in/errgo.v2 v2.1.0 ) diff --git a/go.sum b/go.sum index ab0108620..e182965de 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,20 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw= -github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/MinterTeam/events-db v0.1.0/go.mod h1:ZnKNYUSFUgVTTB359LvKQbroYqBiriXmbnyaMsULU2o= -github.com/MinterTeam/events-db v0.1.1-0.20191113155258-73206cb9a3ea/go.mod h1:INfo4gbiX8/EGfjHmRvvW2+5YuOcTy7Tob69yUhAqU4= -github.com/MinterTeam/events-db v0.1.6 h1:kxUPuzmbwSTva7jHOlydsvBFhGAIgkpsXT8xAnQG8yA= -github.com/MinterTeam/events-db v0.1.6/go.mod h1:niLCQF2EmB3G27zHj94jUO3p359sXEvTcdZrt8dS2AQ= -github.com/MinterTeam/go-amino v0.14.1/go.mod h1:2sqs1fcf3DcQpvIJarkzVCjALWc11WDwc5oFHCrG5R4= -github.com/MinterTeam/go-amino v0.14.2-0.20191113110031-d5499d43f453 h1:uHtg6ZR5wVNJhwVI0Gw1RPPip6sj6Mo2SqoAh8scGp0= -github.com/MinterTeam/go-amino v0.14.2-0.20191113110031-d5499d43f453/go.mod h1:o4p3WDNrrIOapHOfM5pKJNZ1exPI+c6ZvbemrNapJxc= -github.com/MinterTeam/minter-go-node v1.0.4/go.mod h1:juaQ5woUKkGknh4Fj57+uv0AgMAzSv/aChNQT04K+uQ= -github.com/MinterTeam/minter-go-node v1.0.5-0.20191113110340-a46b8ef88084/go.mod h1:ihc9SIXO0ZSnnS6AB2kT+UD9iIHap0TH47BvL6A2fQg= -github.com/MinterTeam/minter-go-node v1.1.0-beta5/go.mod h1:MeO8y1SPL2uk/znxFPauMB4karv/xQeI1O1SPeEycrU= -github.com/MinterTeam/minter-node-cli v0.0.0-20191202172005-309e9d40a5a3 h1:2ndNow11LzKNggx2JNh2Fhb0NKEQ2kVt+HL9jdEBLOg= -github.com/MinterTeam/minter-node-cli v0.0.0-20191202172005-309e9d40a5a3/go.mod h1:6iFtZjWsvR9ZaXaogfDkIYVSrQAHQBk/tU1L5yKS5/8= -github.com/MinterTeam/node-grpc-gateway v1.0.0 h1:E2Vn3wC0kv/zgzA1biC84+M2Y4h0k2etLXzTM6jJyeI= -github.com/MinterTeam/node-grpc-gateway v1.0.0/go.mod h1:LJJNHzYkAbWsoWZw3VMgs+NQTnJ3f0YawF2UZxevTOE= +github.com/MinterTeam/node-grpc-gateway v1.2.1-0.20201019072706-326f69c6e526 h1:GjPRupQehjcPy9WLhrr7+HgnsnaAaQUcWbpkiDSAmLk= +github.com/MinterTeam/node-grpc-gateway v1.2.1-0.20201019072706-326f69c6e526/go.mod h1:49888tivqufwv7KMC0QVZLGJP3wW2cQQazccvOYWCiE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -32,7 +22,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -49,11 +39,12 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw= -github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= +github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= -github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= @@ -75,6 +66,7 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -103,9 +95,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= -github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= @@ -113,7 +102,6 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -125,7 +113,7 @@ github.com/gdamore/encoding v0.0.0-20151215212835-b23993cbb635/go.mod h1:yrQYJKK github.com/gdamore/tcell v1.1.0 h1:RbQgl7jukmdqROeNcKps7R2YfDCQbWkOd1BwdXrxfr4= github.com/gdamore/tcell v1.1.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -139,13 +127,10 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/packr v1.11.1 h1:L44og8g+xWusakqZ5N52ujRSopFNtXextBGJgn69D28= -github.com/gobuffalo/packr v1.11.1/go.mod h1:rYwMLC6NXbAbkKb+9j3NTKbxSswkKLlelZYccr4HYVw= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= @@ -153,7 +138,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= @@ -161,8 +145,15 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -175,6 +166,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -187,24 +180,31 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.12.0 h1:SFRyYOyhgiU1kJG/PmbkWP/iSlizvDJEz531dq5kneg= -github.com/grpc-ecosystem/grpc-gateway v1.12.0/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= +github.com/grpc-ecosystem/grpc-gateway v1.15.0 h1:ntPNC9TD/6l2XDenJZe6T5lSMg95thpV9sGAqHX4WU8= +github.com/grpc-ecosystem/grpc-gateway v1.15.0/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -293,6 +293,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/minio/highwayhash v1.0.0 h1:iMSDhgUILCr0TNm8LWlSjF8N0ZIj2qbO8WHp6Q/J2BA= +github.com/minio/highwayhash v1.0.0/go.mod h1:xQboMTeM9nY9v/LlAOxFctujiv5+Aq2hR5dxBpaMbdc= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -359,8 +361,6 @@ github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAy github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= -github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -371,7 +371,6 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= @@ -388,14 +387,15 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= -github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= +github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= @@ -422,22 +422,19 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= -github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= -github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= +github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -449,45 +446,30 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stumble/gorocksdb v0.0.3 h1:9UU+QA1pqFYJuf9+5p7z1IqdE5k0mma4UAeu2wmX8kA= -github.com/stumble/gorocksdb v0.0.3/go.mod h1:v6IHdFBXk5DJ1K4FZ0xi+eY737quiiBxYtSWXadLybY= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= -github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= -github.com/tecbot/gorocksdb v0.0.0-20191017175515-d217d93fd4c5 h1:gVwAW5OwaZlDB5/CfqcGFM9p9C+KxvQKyNOltQ8orj0= -github.com/tecbot/gorocksdb v0.0.0-20191017175515-d217d93fd4c5/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.1 h1:D2uk35eT4iTsvJd9jWIetzthE5C0/k2QmMFkCN+4JgQ= github.com/tendermint/go-amino v0.15.1/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8= -github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o= -github.com/tendermint/iavl v0.13.2 h1:O1m08/Ciy53l9IYmf75uIRVvrNsfjEbre8u/yCu/oqk= -github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1TuoljBcsQoGA= -github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU= -github.com/tendermint/tendermint v0.32.6 h1:HozXi0USWvKrWuEh5ratnJV10ykkTy4nwXUi0UvPVzg= -github.com/tendermint/tendermint v0.32.6/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8dh/Zg640FGyGtE= -github.com/tendermint/tendermint v0.32.8/go.mod h1:5/B1XZjNYtVBso8o1l/Eg4A0Mhu42lDcmftoQl95j/E= -github.com/tendermint/tendermint v0.33.2 h1:NzvRMTuXJxqSsFed2J7uHmMU5N1CVzSpfi3nCc882KY= -github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= -github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI= -github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= -github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= -github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ= -github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= -github.com/tendermint/tm-db v0.4.0 h1:iPbCcLbf4nwDFhS39Zo1lpdS1X/cT9CkTlUx17FHQgA= -github.com/tendermint/tm-db v0.4.0/go.mod h1:+Cwhgowrf7NBGXmsqFMbwEtbo80XmyrlY5Jsk95JubQ= -github.com/tendermint/tm-db v0.4.1 h1:TvX7JWjJOVZ+N3y+I86wddrGttOdMmmBxXcu0/Y7ZJ0= -github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY= -github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ= -github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= +github.com/tendermint/iavl v0.14.0 h1:Jkff+IFrXxRWtH9Jn/ga/2cxNnzMTv58xEKgCJsKUBg= +github.com/tendermint/iavl v0.14.0/go.mod h1:QmfViflFiXzxKLQE4tAUuWQHq+RSuQFxablW5oJZ6sE= +github.com/tendermint/tendermint v0.33.5 h1:jYgRd9ImkzA9iOyhpmgreYsqSB6tpDa6/rXYPb8HKE8= +github.com/tendermint/tendermint v0.33.5/go.mod h1:0yUs9eIuuDq07nQql9BmI30FtYGcEC60Tu5JzB5IezM= +github.com/tendermint/tendermint v0.33.8 h1:Xxu4QhpqcomSE0iQDw1MqLgfsa8fqtPtWFJK6zZOVso= +github.com/tendermint/tendermint v0.33.8/go.mod h1:0yUs9eIuuDq07nQql9BmI30FtYGcEC60Tu5JzB5IezM= +github.com/tendermint/tm-db v0.5.1 h1:H9HDq8UEA7Eeg13kdYckkgwwkQLBnJGgX4PgLJRhieY= +github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966 h1:j6JEOq5QWFker+d7mFQYOhjTZonQ7YkLTHm56dbn+yM= +github.com/tmc/grpc-websocket-proxy v0.0.0-20200427203606-3cfed13b9966/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= @@ -514,29 +496,38 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= +golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -554,8 +545,14 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -564,6 +561,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEha golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -572,21 +571,27 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -607,47 +612,63 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181109154231-b5d43981345b/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8= +google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.0 h1:ItERT+UbGdX+s4u+nQNlVM/Q7cbmf7icKfvzbWqVtq0= -google.golang.org/grpc v1.25.0/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -657,6 +678,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -676,6 +698,8 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/helpers/helpers.go b/helpers/helpers.go index 61e23ad86..3c07cf4a3 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -1,10 +1,12 @@ package helpers import ( + "errors" "fmt" "math/big" ) +// BipToPip converts BIP to PIP (multiplies input by 1e18) func BipToPip(bip *big.Int) *big.Int { p := big.NewInt(10) p.Exp(p, big.NewInt(18), nil) @@ -13,19 +15,30 @@ func BipToPip(bip *big.Int) *big.Int { return p } +// StringToBigInt converts string to BigInt, panics on empty strings and errors func StringToBigInt(s string) *big.Int { + result, err := stringToBigInt(s) + if err != nil { + panic(err) + } + + return result +} + +func stringToBigInt(s string) (*big.Int, error) { if s == "" { - panic("string is empty") + return nil, errors.New("string is empty") } b, success := big.NewInt(0).SetString(s, 10) if !success { - panic(fmt.Sprintf("Cannot decode %s into big.Int", s)) + return nil, fmt.Errorf("cannot decode %s into big.Int", s) } - return b + return b, nil } +// IsValidBigInt verifies that string is a valid int func IsValidBigInt(s string) bool { if s == "" { return false diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go new file mode 100644 index 000000000..46464c3af --- /dev/null +++ b/helpers/helpers_test.go @@ -0,0 +1,53 @@ +package helpers + +import ( + "math/big" + "testing" +) + +func TestIsValidBigInt(t *testing.T) { + cases := map[string]bool{ + "": false, + "1": true, + "1s": false, + "-1": false, + "123437456298465928764598276349587623948756928764958762934569": true, + } + + for str, result := range cases { + if IsValidBigInt(str) != result { + t.Fail() + } + } +} + +func TestStringToBigInt(t *testing.T) { + cases := map[string]bool{ + "": false, + "1": true, + "1s": false, + "-1": true, + "123437456298465928764598276349587623948756928764958762934569": true, + } + + for str, result := range cases { + _, err := stringToBigInt(str) + + if err != nil && result || err == nil && !result { + t.Fatalf("%s %s", err, str) + } + } + + result := StringToBigInt("10") + if result.Cmp(big.NewInt(10)) != 0 { + t.Fail() + } +} + +func TestBipToPip(t *testing.T) { + pip := BipToPip(big.NewInt(1)) + + if pip.Cmp(big.NewInt(1000000000000000000)) != 0 { + t.Fail() + } +} diff --git a/hexutil/hexutil.go b/hexutil/hexutil.go index bd1f0e9b5..86abadf80 100644 --- a/hexutil/hexutil.go +++ b/hexutil/hexutil.go @@ -39,6 +39,7 @@ import ( const uintBits = 32 << (uint64(^uint(0)) >> 63) +// Error set var ( ErrEmptyString = &decError{"empty hex string"} ErrSyntax = &decError{"invalid hex string"} @@ -181,7 +182,7 @@ func EncodeBig(bigint *big.Int) string { if nbits == 0 { return "Mx0" } - return fmt.Sprintf("%#x", bigint) + return fmt.Sprintf("Mx%x", bigint) } func hasMxPrefix(input string) bool { diff --git a/hexutil/hexutil_test.go b/hexutil/hexutil_test.go new file mode 100644 index 000000000..abc1f564e --- /dev/null +++ b/hexutil/hexutil_test.go @@ -0,0 +1,203 @@ +// Copyright 2016 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 hexutil + +import ( + "bytes" + "math/big" + "testing" +) + +type marshalTest struct { + input interface{} + want string +} + +type unmarshalTest struct { + input string + want interface{} + wantErr error // if set, decoding must fail on any platform + wantErr32bit error // if set, decoding must fail on 32bit platforms (used for Uint tests) +} + +var ( + encodeBytesTests = []marshalTest{ + {[]byte{}, "Mx"}, + {[]byte{0}, "Mx00"}, + {[]byte{0, 0, 1, 2}, "Mx00000102"}, + } + + encodeBigTests = []marshalTest{ + {referenceBig("0"), "Mx0"}, + {referenceBig("1"), "Mx1"}, + {referenceBig("ff"), "Mxff"}, + {referenceBig("112233445566778899aabbccddeeff"), "Mx112233445566778899aabbccddeeff"}, + {referenceBig("80a7f2c1bcc396c00"), "Mx80a7f2c1bcc396c00"}, + {referenceBig("-80a7f2c1bcc396c00"), "Mx-80a7f2c1bcc396c00"}, + } + + encodeUint64Tests = []marshalTest{ + {uint64(0), "Mx0"}, + {uint64(1), "Mx1"}, + {uint64(0xff), "Mxff"}, + {uint64(0x1122334455667788), "Mx1122334455667788"}, + } + + encodeUintTests = []marshalTest{ + {uint(0), "Mx0"}, + {uint(1), "Mx1"}, + {uint(0xff), "Mxff"}, + {uint(0x11223344), "Mx11223344"}, + } + + decodeBytesTests = []unmarshalTest{ + // invalid + {input: ``, wantErr: ErrEmptyString}, + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `Mx0`, wantErr: ErrOddLength}, + {input: `Mx023`, wantErr: ErrOddLength}, + {input: `Mxxx`, wantErr: ErrSyntax}, + {input: `Mx01zz01`, wantErr: ErrSyntax}, + // valid + {input: `Mx`, want: []byte{}}, + {input: `MX`, want: []byte{}}, + {input: `Mx02`, want: []byte{0x02}}, + {input: `MX02`, want: []byte{0x02}}, + {input: `Mxffffffffff`, want: []byte{0xff, 0xff, 0xff, 0xff, 0xff}}, + { + input: `Mxffffffffffffffffffffffffffffffffffff`, + want: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }, + } + + decodeBigTests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `Mx`, wantErr: ErrEmptyNumber}, + {input: `Mx01`, wantErr: ErrLeadingZero}, + {input: `Mxx`, wantErr: ErrSyntax}, + {input: `Mx1zz01`, wantErr: ErrSyntax}, + { + input: `Mx10000000000000000000000000000000000000000000000000000000000000000`, + wantErr: ErrBig256Range, + }, + // valid + {input: `Mx0`, want: big.NewInt(0)}, + {input: `Mx2`, want: big.NewInt(0x2)}, + {input: `Mx2F2`, want: big.NewInt(0x2f2)}, + {input: `MX2F2`, want: big.NewInt(0x2f2)}, + {input: `Mx1122aaff`, want: big.NewInt(0x1122aaff)}, + {input: `MxbBb`, want: big.NewInt(0xbbb)}, + {input: `Mxfffffffff`, want: big.NewInt(0xfffffffff)}, + { + input: `Mx112233445566778899aabbccddeeff`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `Mxffffffffffffffffffffffffffffffffffff`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + { + input: `Mxffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`, + want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, + } + + decodeUint64Tests = []unmarshalTest{ + // invalid + {input: `0`, wantErr: ErrMissingPrefix}, + {input: `Mx`, wantErr: ErrEmptyNumber}, + {input: `Mx01`, wantErr: ErrLeadingZero}, + {input: `Mxfffffffffffffffff`, wantErr: ErrUint64Range}, + {input: `Mxx`, wantErr: ErrSyntax}, + {input: `Mx1zz01`, wantErr: ErrSyntax}, + // valid + {input: `Mx0`, want: uint64(0)}, + {input: `Mx2`, want: uint64(0x2)}, + {input: `Mx2F2`, want: uint64(0x2f2)}, + {input: `MX2F2`, want: uint64(0x2f2)}, + {input: `Mx1122aaff`, want: uint64(0x1122aaff)}, + {input: `Mxbbb`, want: uint64(0xbbb)}, + {input: `Mxffffffffffffffff`, want: uint64(0xffffffffffffffff)}, + } +) + +func TestEncode(t *testing.T) { + for _, test := range encodeBytesTests { + enc := Encode(test.input.([]byte)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecode(t *testing.T) { + for _, test := range decodeBytesTests { + dec, err := Decode(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), dec) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeBig(t *testing.T) { + for _, test := range encodeBigTests { + enc := EncodeBig(test.input.(*big.Int)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeBig(t *testing.T) { + for _, test := range decodeBigTests { + dec, err := DecodeBig(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec.Cmp(test.want.(*big.Int)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} + +func TestEncodeUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + enc := EncodeUint64(test.input.(uint64)) + if enc != test.want { + t.Errorf("input %x: wrong encoding %s", test.input, enc) + } + } +} + +func TestDecodeUint64(t *testing.T) { + for _, test := range decodeUint64Tests { + dec, err := DecodeUint64(test.input) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if dec != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, dec, test.want) + continue + } + } +} diff --git a/hexutil/json.go b/hexutil/json.go index 407cda9b6..618c4f32d 100644 --- a/hexutil/json.go +++ b/hexutil/json.go @@ -273,7 +273,7 @@ func isString(input []byte) bool { return len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' } -func bytesHave0xPrefix(input []byte) bool { +func bytesHaveMxPrefix(input []byte) bool { return len(input) >= 2 && input[0] == 'M' && (input[1] == 'x' || input[1] == 'X') } @@ -281,7 +281,7 @@ func checkText(input []byte, wantPrefix bool) ([]byte, error) { if len(input) == 0 { return nil, nil // empty strings are allowed } - if bytesHave0xPrefix(input) { + if bytesHaveMxPrefix(input) { input = input[2:] } else if wantPrefix { return nil, ErrMissingPrefix @@ -296,7 +296,7 @@ func checkNumberText(input []byte) (raw []byte, err error) { if len(input) == 0 { return nil, nil // empty strings are allowed } - if !bytesHave0xPrefix(input) { + if !bytesHaveMxPrefix(input) { return nil, ErrMissingPrefix } input = input[2:] diff --git a/hexutil/json_test.go b/hexutil/json_test.go new file mode 100644 index 000000000..b7cbf67c5 --- /dev/null +++ b/hexutil/json_test.go @@ -0,0 +1,374 @@ +// Copyright 2016 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 hexutil + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "errors" + "math/big" + "testing" +) + +func checkError(t *testing.T, input string, got, want error) bool { + if got == nil { + if want != nil { + t.Errorf("input %s: got no error, want %q", input, want) + return false + } + return true + } + if want == nil { + t.Errorf("input %s: unexpected error %q", input, got) + } else if got.Error() != want.Error() { + t.Errorf("input %s: got error %q, want %q", input, got, want) + } + return false +} + +func referenceBig(s string) *big.Int { + b, ok := new(big.Int).SetString(s, 16) + if !ok { + panic("invalid") + } + return b +} + +func referenceBytes(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +var errJSONEOF = errors.New("unexpected end of JSON input") + +var unmarshalBytesTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bytesT)}, + {input: "10", wantErr: errNonString(bytesT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bytesT)}, + {input: `"Mx0"`, wantErr: wrapTypeError(ErrOddLength, bytesT)}, + {input: `"Mxxx"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + {input: `"Mx01zz01"`, wantErr: wrapTypeError(ErrSyntax, bytesT)}, + + // valid encoding + {input: `""`, want: referenceBytes("")}, + {input: `"Mx"`, want: referenceBytes("")}, + {input: `"Mx02"`, want: referenceBytes("02")}, + {input: `"MX02"`, want: referenceBytes("02")}, + {input: `"Mxffffffffff"`, want: referenceBytes("ffffffffff")}, + { + input: `"Mxffffffffffffffffffffffffffffffffffff"`, + want: referenceBytes("ffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBytes(t *testing.T) { + for _, test := range unmarshalBytesTests { + var v Bytes + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if !bytes.Equal(test.want.([]byte), []byte(v)) { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, &v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + input := []byte(`"Mx123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Bytes + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBytes(t *testing.T) { + for _, test := range encodeBytesTests { + in := test.input.([]byte) + out, err := json.Marshal(Bytes(in)) + if err != nil { + t.Errorf("%x: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%x: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := Bytes(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalBigTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(bigT)}, + {input: "10", wantErr: errNonString(bigT)}, + {input: `"0"`, wantErr: wrapTypeError(ErrMissingPrefix, bigT)}, + {input: `"Mx"`, wantErr: wrapTypeError(ErrEmptyNumber, bigT)}, + {input: `"Mx01"`, wantErr: wrapTypeError(ErrLeadingZero, bigT)}, + {input: `"Mxx"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + {input: `"Mx1zz01"`, wantErr: wrapTypeError(ErrSyntax, bigT)}, + { + input: `"Mx10000000000000000000000000000000000000000000000000000000000000000"`, + wantErr: wrapTypeError(ErrBig256Range, bigT), + }, + + // valid encoding + {input: `""`, want: big.NewInt(0)}, + {input: `"Mx0"`, want: big.NewInt(0)}, + {input: `"Mx2"`, want: big.NewInt(0x2)}, + {input: `"Mx2F2"`, want: big.NewInt(0x2f2)}, + {input: `"MX2F2"`, want: big.NewInt(0x2f2)}, + {input: `"Mx1122aaff"`, want: big.NewInt(0x1122aaff)}, + {input: `"MxbBb"`, want: big.NewInt(0xbbb)}, + {input: `"Mxfffffffff"`, want: big.NewInt(0xfffffffff)}, + { + input: `"Mx112233445566778899aabbccddeeff"`, + want: referenceBig("112233445566778899aabbccddeeff"), + }, + { + input: `"Mxffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffff"), + }, + { + input: `"Mxffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"`, + want: referenceBig("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), + }, +} + +func TestUnmarshalBig(t *testing.T) { + for _, test := range unmarshalBigTests { + var v Big + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if test.want != nil && test.want.(*big.Int).Cmp((*big.Int)(&v)) != 0 { + t.Errorf("input %s: value mismatch: got %x, want %x", test.input, (*big.Int)(&v), test.want) + continue + } + } +} + +func BenchmarkUnmarshalBig(b *testing.B) { + input := []byte(`"Mx123456789abcdef123456789abcdef"`) + for i := 0; i < b.N; i++ { + var v Big + if err := v.UnmarshalJSON(input); err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalBig(t *testing.T) { + for _, test := range encodeBigTests { + in := test.input.(*big.Int) + out, err := json.Marshal((*Big)(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (*Big)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var unmarshalUint64Tests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uint64T)}, + {input: "10", wantErr: errNonString(uint64T)}, + {input: `"M"`, wantErr: wrapTypeError(ErrMissingPrefix, uint64T)}, + {input: `"Mx"`, wantErr: wrapTypeError(ErrEmptyNumber, uint64T)}, + {input: `"Mx01"`, wantErr: wrapTypeError(ErrLeadingZero, uint64T)}, + {input: `"Mxfffffffffffffffff"`, wantErr: wrapTypeError(ErrUint64Range, uint64T)}, + {input: `"Mxx"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + {input: `"Mx1zz01"`, wantErr: wrapTypeError(ErrSyntax, uint64T)}, + + // valid encoding + {input: `""`, want: uint64(0)}, + {input: `"Mx0"`, want: uint64(0)}, + {input: `"Mx2"`, want: uint64(0x2)}, + {input: `"Mx2F2"`, want: uint64(0x2f2)}, + {input: `"MX2F2"`, want: uint64(0x2f2)}, + {input: `"Mx1122aaff"`, want: uint64(0x1122aaff)}, + {input: `"Mxbbb"`, want: uint64(0xbbb)}, + {input: `"Mxffffffffffffffff"`, want: uint64(0xffffffffffffffff)}, +} + +func TestUnmarshalUint64(t *testing.T) { + for _, test := range unmarshalUint64Tests { + var v Uint64 + err := json.Unmarshal([]byte(test.input), &v) + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint64(v) != test.want.(uint64) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func BenchmarkUnmarshalUint64(b *testing.B) { + input := []byte(`"Mx123456789abcdf"`) + for i := 0; i < b.N; i++ { + var v Uint64 + v.UnmarshalJSON(input) + } +} + +func TestMarshalUint64(t *testing.T) { + for _, test := range encodeUint64Tests { + in := test.input.(uint64) + out, err := json.Marshal(Uint64(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint64)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +func TestMarshalUint(t *testing.T) { + for _, test := range encodeUintTests { + in := test.input.(uint) + out, err := json.Marshal(Uint(in)) + if err != nil { + t.Errorf("%d: %v", in, err) + continue + } + if want := `"` + test.want + `"`; string(out) != want { + t.Errorf("%d: MarshalJSON output mismatch: got %q, want %q", in, out, want) + continue + } + if out := (Uint)(in).String(); out != test.want { + t.Errorf("%x: String mismatch: got %q, want %q", in, out, test.want) + continue + } + } +} + +var ( + // These are variables (not constants) to avoid constant overflow + // checks in the compiler on 32bit platforms. + maxUint33bits = uint64(^uint32(0)) + 1 + maxUint64bits = ^uint64(0) +) + +var unmarshalUintTests = []unmarshalTest{ + // invalid encoding + {input: "", wantErr: errJSONEOF}, + {input: "null", wantErr: errNonString(uintT)}, + {input: "10", wantErr: errNonString(uintT)}, + {input: `"M"`, wantErr: wrapTypeError(ErrMissingPrefix, uintT)}, + {input: `"Mx"`, wantErr: wrapTypeError(ErrEmptyNumber, uintT)}, + {input: `"Mx01"`, wantErr: wrapTypeError(ErrLeadingZero, uintT)}, + {input: `"Mx100000000"`, want: uint(maxUint33bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, + {input: `"Mxfffffffffffffffff"`, wantErr: wrapTypeError(ErrUintRange, uintT)}, + {input: `"Mxx"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + {input: `"Mx1zz01"`, wantErr: wrapTypeError(ErrSyntax, uintT)}, + + // valid encoding + {input: `""`, want: uint(0)}, + {input: `"Mx0"`, want: uint(0)}, + {input: `"Mx2"`, want: uint(0x2)}, + {input: `"Mx2F2"`, want: uint(0x2f2)}, + {input: `"MX2F2"`, want: uint(0x2f2)}, + {input: `"Mx1122aaff"`, want: uint(0x1122aaff)}, + {input: `"Mxbbb"`, want: uint(0xbbb)}, + {input: `"Mxffffffff"`, want: uint(0xffffffff)}, + {input: `"Mxffffffffffffffff"`, want: uint(maxUint64bits), wantErr32bit: wrapTypeError(ErrUintRange, uintT)}, +} + +func TestUnmarshalUint(t *testing.T) { + for _, test := range unmarshalUintTests { + var v Uint + err := json.Unmarshal([]byte(test.input), &v) + if uintBits == 32 && test.wantErr32bit != nil { + checkError(t, test.input, err, test.wantErr32bit) + continue + } + if !checkError(t, test.input, err, test.wantErr) { + continue + } + if uint(v) != test.want.(uint) { + t.Errorf("input %s: value mismatch: got %d, want %d", test.input, v, test.want) + continue + } + } +} + +func TestUnmarshalFixedUnprefixedText(t *testing.T) { + tests := []struct { + input string + want []byte + wantErr error + }{ + {input: "0x2", wantErr: ErrOddLength}, + {input: "2", wantErr: ErrOddLength}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + {input: "4444", wantErr: errors.New("hex string has length 4, want 8 for x")}, + // check that output is not modified for partially correct input + {input: "444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + {input: "Mx444444gg", wantErr: ErrSyntax, want: []byte{0, 0, 0, 0}}, + // valid inputs + {input: "44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + {input: "Mx44444444", want: []byte{0x44, 0x44, 0x44, 0x44}}, + } + + for _, test := range tests { + out := make([]byte, 4) + err := UnmarshalFixedUnprefixedText("x", []byte(test.input), out) + switch { + case err == nil && test.wantErr != nil: + t.Errorf("%q: got no error, expected %q", test.input, test.wantErr) + case err != nil && test.wantErr == nil: + t.Errorf("%q: unexpected error %q", test.input, err) + case err != nil && err.Error() != test.wantErr.Error(): + t.Errorf("%q: error mismatch: got %q, want %q", test.input, err, test.wantErr) + } + if test.want != nil && !bytes.Equal(out, test.want) { + t.Errorf("%q: output mismatch: got %x, want %x", test.input, out, test.want) + } + } +} diff --git a/legacy/accounts/accounts.go b/legacy/accounts/accounts.go new file mode 100644 index 000000000..b330833ad --- /dev/null +++ b/legacy/accounts/accounts.go @@ -0,0 +1,210 @@ +package accounts + +import ( + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "math/big" + "sort" + "sync" +) + +const mainPrefix = byte('a') +const coinsPrefix = byte('c') +const balancePrefix = byte('b') + +type Accounts struct { + list map[types.Address]*Model + dirty map[types.Address]struct{} + + iavl tree.MTree + bus *bus.Bus + + lock sync.RWMutex +} + +func NewAccounts(stateBus *bus.Bus, iavl tree.MTree) (*Accounts, error) { + accounts := &Accounts{iavl: iavl, bus: stateBus, list: map[types.Address]*Model{}, dirty: map[types.Address]struct{}{}} + return accounts, nil +} + +func (a *Accounts) GetBalance(address types.Address, coin types.CoinSymbol) *big.Int { + account := a.getOrNew(address) + if !account.hasCoin(coin) { + return big.NewInt(0) + } + + if _, ok := account.balances[coin]; !ok { + balance := big.NewInt(0) + + path := []byte{mainPrefix} + path = append(path, address[:]...) + path = append(path, balancePrefix) + path = append(path, coin[:]...) + + _, enc := a.iavl.Get(path) + if len(enc) != 0 { + balance = big.NewInt(0).SetBytes(enc) + } + + account.balances[coin] = balance + } + + return big.NewInt(0).Set(account.balances[coin]) +} + +func (a *Accounts) get(address types.Address) *Model { + if account := a.getFromMap(address); account != nil { + return account + } + + path := []byte{mainPrefix} + path = append(path, address[:]...) + _, enc := a.iavl.Get(path) + if len(enc) == 0 { + return nil + } + + account := &Model{} + if err := rlp.DecodeBytes(enc, account); err != nil { + panic(fmt.Sprintf("failed to decode account at address %s: %s", address.String(), err)) + } + + account.address = address + account.balances = map[types.CoinSymbol]*big.Int{} + account.markDirty = a.markDirty + account.dirtyBalances = map[types.CoinSymbol]struct{}{} + + // load coins + path = []byte{mainPrefix} + path = append(path, address[:]...) + path = append(path, coinsPrefix) + _, enc = a.iavl.Get(path) + if len(enc) != 0 { + var coins []types.CoinSymbol + if err := rlp.DecodeBytes(enc, &coins); err != nil { + panic(fmt.Sprintf("failed to decode coins list at address %s: %s", address.String(), err)) + } + + account.coins = coins + } + + a.setToMap(address, account) + return account +} + +func (a *Accounts) getOrNew(address types.Address) *Model { + account := a.get(address) + if account == nil { + account = &Model{ + Nonce: 0, + address: address, + coins: []types.CoinSymbol{}, + balances: map[types.CoinSymbol]*big.Int{}, + markDirty: a.markDirty, + dirtyBalances: map[types.CoinSymbol]struct{}{}, + isNew: true, + } + a.setToMap(address, account) + } + + return account +} + +func (a *Accounts) GetNonce(address types.Address) uint64 { + account := a.getOrNew(address) + + return account.Nonce +} + +func (a *Accounts) GetBalances(address types.Address) map[types.CoinSymbol]*big.Int { + account := a.getOrNew(address) + + balances := make(map[types.CoinSymbol]*big.Int, len(account.coins)) + for _, coin := range account.coins { + balances[coin] = a.GetBalance(address, coin) + } + + return balances +} + +func (a *Accounts) markDirty(addr types.Address) { + a.dirty[addr] = struct{}{} +} + +func (a *Accounts) Export(state *types.AppState, coinsMap map[types.CoinSymbol]types.Coin) { + // todo: iterate range? + a.iavl.Iterate(func(key []byte, value []byte) bool { + if key[0] == mainPrefix { + addressPath := key[1:] + if len(addressPath) > types.AddressLength { + return false + } + + address := types.BytesToAddress(addressPath) + account := a.get(address) + + var balance []types.Balance + for coinSymbol, value := range a.GetBalances(account.address) { + coin := coinsMap[coinSymbol] + + // set account address as owner address of coin if account contains whole volume + if coin.Reserve == value.String() { + state.Coins[coin.ID-1].OwnerAddress = &account.address + } + + balance = append(balance, types.Balance{ + Coin: coin.ID, + Value: value.String(), + }) + } + + // sort balances by coin symbol + sort.SliceStable(balance, func(i, j int) bool { + return balance[i].Coin > balance[j].Coin + }) + + acc := types.Account{ + Address: account.address, + Balance: balance, + Nonce: account.Nonce, + } + + if account.IsMultisig() { + var weights []uint64 + for _, weight := range account.MultisigData.Weights { + weights = append(weights, uint64(weight)) + } + acc.MultisigData = &types.Multisig{ + Weights: weights, + Threshold: uint64(account.MultisigData.Threshold), + Addresses: account.MultisigData.Addresses, + } + } + + state.Accounts = append(state.Accounts, acc) + } + + return false + }) +} + +func (a *Accounts) GetAccount(address types.Address) *Model { + return a.getOrNew(address) +} + +func (a *Accounts) getFromMap(address types.Address) *Model { + a.lock.RLock() + defer a.lock.RUnlock() + + return a.list[address] +} + +func (a *Accounts) setToMap(address types.Address, model *Model) { + a.lock.Lock() + defer a.lock.Unlock() + + a.list[address] = model +} diff --git a/legacy/accounts/model.go b/legacy/accounts/model.go new file mode 100644 index 000000000..ef67c6fb4 --- /dev/null +++ b/legacy/accounts/model.go @@ -0,0 +1,138 @@ +package accounts + +import ( + "bytes" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/rlp" + "math/big" + "sort" +) + +type Model struct { + Nonce uint64 + MultisigData Multisig + + address types.Address + coins []types.CoinSymbol + balances map[types.CoinSymbol]*big.Int + + hasDirtyCoins bool + dirtyBalances map[types.CoinSymbol]struct{} + isDirty bool // nonce or multisig data + + isNew bool + + markDirty func(types.Address) +} + +type Multisig struct { + Threshold uint32 + Weights []uint32 + Addresses []types.Address +} + +func (m *Multisig) Address() types.Address { + b, err := rlp.EncodeToBytes(m) + if err != nil { + panic(err) + } + + var addr types.Address + copy(addr[:], crypto.Keccak256(b)[12:]) + + return addr +} + +func (m *Multisig) GetWeight(address types.Address) uint32 { + for i, addr := range m.Addresses { + if addr == address { + return m.Weights[i] + } + } + + return 0 +} + +func (model *Model) setNonce(nonce uint64) { + model.Nonce = nonce + model.isDirty = true + model.markDirty(model.address) +} + +func (model *Model) getBalance(coin types.CoinSymbol) *big.Int { + return model.balances[coin] +} + +func (model *Model) hasDirtyBalances() bool { + return len(model.dirtyBalances) > 0 +} + +func (model *Model) isBalanceDirty(coin types.CoinSymbol) bool { + _, exists := model.dirtyBalances[coin] + return exists +} + +func (model *Model) getOrderedCoins() []types.CoinSymbol { + keys := make([]types.CoinSymbol, 0, len(model.balances)) + for k := range model.balances { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} + +func (model *Model) setBalance(coin types.CoinSymbol, amount *big.Int) { + if amount.Cmp(big.NewInt(0)) == 0 { + if !model.hasCoin(coin) { + return + } + + var newCoins []types.CoinSymbol + for _, c := range model.coins { + if coin == c { + continue + } + + newCoins = append(newCoins, c) + } + + model.hasDirtyCoins = true + model.coins = newCoins + model.balances[coin] = amount + model.dirtyBalances[coin] = struct{}{} + model.markDirty(model.address) + + return + } + + if !model.hasCoin(coin) { + model.hasDirtyCoins = true + model.coins = append(model.coins, coin) + } + model.dirtyBalances[coin] = struct{}{} + model.markDirty(model.address) + model.balances[coin] = amount +} + +func (model *Model) hasCoin(coin types.CoinSymbol) bool { + for _, c := range model.coins { + if c == coin { + return true + } + } + + return false +} + +func (model *Model) IsMultisig() bool { + return len(model.MultisigData.Weights) > 0 +} + +func (model *Model) Multisig() Multisig { + return model.MultisigData +} diff --git a/legacy/app/app.go b/legacy/app/app.go new file mode 100644 index 000000000..ac38ca66d --- /dev/null +++ b/legacy/app/app.go @@ -0,0 +1,106 @@ +package app + +import ( + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "math/big" +) + +const mainPrefix = 'd' + +func (v *App) Tree() tree.ReadOnlyTree { + return v.iavl +} + +type App struct { + model *Model + isDirty bool + + bus *bus.Bus + iavl tree.MTree +} + +func NewApp(stateBus *bus.Bus, iavl tree.MTree) (*App, error) { + app := &App{bus: stateBus, iavl: iavl} + + return app, nil +} + +func (v *App) GetMaxGas() uint64 { + model := v.getOrNew() + + return model.getMaxGas() +} + +func (v *App) SetMaxGas(gas uint64) { + model := v.getOrNew() + model.setMaxGas(gas) +} + +func (v *App) GetTotalSlashed() *big.Int { + model := v.getOrNew() + + return model.getTotalSlashed() +} + +func (v *App) AddTotalSlashed(amount *big.Int) { + if amount.Cmp(big.NewInt(0)) == 0 { + return + } + + model := v.getOrNew() + model.setTotalSlashed(big.NewInt(0).Add(model.getTotalSlashed(), amount)) + v.bus.Checker().AddCoin(types.GetBaseCoinID(), amount) +} + +func (v *App) get() *Model { + if v.model != nil { + return v.model + } + + path := []byte{mainPrefix} + _, enc := v.iavl.Get(path) + if len(enc) == 0 { + return nil + } + + model := &Model{} + if err := rlp.DecodeBytes(enc, model); err != nil { + panic(fmt.Sprintf("failed to decode app model at: %s", err)) + } + + v.model = model + v.model.markDirty = v.markDirty + return v.model +} + +func (v *App) getOrNew() *Model { + model := v.get() + if model == nil { + model = &Model{ + TotalSlashed: big.NewInt(0), + MaxGas: 0, + markDirty: v.markDirty, + } + v.model = model + } + + return model +} + +func (v *App) markDirty() { + v.isDirty = true +} + +func (v *App) SetTotalSlashed(amount *big.Int) { + v.getOrNew().setTotalSlashed(amount) +} + +func (v *App) Export(state *types.AppState, height uint64) { + state.MaxGas = v.GetMaxGas() + state.TotalSlashed = v.GetTotalSlashed().String() + state.StartHeight = height +} diff --git a/legacy/app/model.go b/legacy/app/model.go new file mode 100644 index 000000000..d502226f7 --- /dev/null +++ b/legacy/app/model.go @@ -0,0 +1,36 @@ +package app + +import "math/big" + +type Model struct { + TotalSlashed *big.Int + MaxGas uint64 + + markDirty func() +} + +func (model *Model) getMaxGas() uint64 { + return model.MaxGas +} + +func (model *Model) setMaxGas(maxGas uint64) { + if model.MaxGas != maxGas { + model.markDirty() + } + model.MaxGas = maxGas +} + +func (model *Model) getTotalSlashed() *big.Int { + if model.TotalSlashed == nil { + return big.NewInt(0) + } + + return model.TotalSlashed +} + +func (model *Model) setTotalSlashed(totalSlashed *big.Int) { + if model.TotalSlashed.Cmp(totalSlashed) != 0 { + model.markDirty() + } + model.TotalSlashed = totalSlashed +} diff --git a/legacy/candidates/candidates.go b/legacy/candidates/candidates.go new file mode 100644 index 000000000..5d9d05e8c --- /dev/null +++ b/legacy/candidates/candidates.go @@ -0,0 +1,381 @@ +package candidates + +import ( + "bytes" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "math/big" + "sort" + "sync" +) + +const ( + CandidateStatusOffline = 0x01 + CandidateStatusOnline = 0x02 + + UnbondPeriod = 518400 + MaxDelegatorsPerCandidate = 1000 + + mainPrefix = 'c' + stakesPrefix = 's' + totalStakePrefix = 't' + updatesPrefix = 'u' +) + +type Candidates struct { + list map[types.Pubkey]*Candidate + + iavl tree.MTree + bus *bus.Bus + + lock sync.RWMutex + loaded bool +} + +func NewCandidates(bus *bus.Bus, iavl tree.MTree) (*Candidates, error) { + candidates := &Candidates{iavl: iavl, bus: bus} + + return candidates, nil +} + +func (c *Candidates) GetNewCandidates(valCount int) []Candidate { + var result []Candidate + + candidates := c.GetCandidates() + for _, candidate := range candidates { + if candidate.Status == CandidateStatusOffline { + continue + } + + if candidate.totalBipStake.Cmp(big.NewInt(0)) == 0 { + continue + } + + result = append(result, *candidate) + } + + sort.Slice(result, func(i, j int) bool { + return result[i].totalBipStake.Cmp(result[j].totalBipStake) == 1 + }) + + if len(result) > valCount { + result = result[:valCount] + } + + return result +} + +func (c *Candidates) Create(ownerAddress types.Address, rewardAddress types.Address, pubkey types.Pubkey, commission uint32) { + candidate := &Candidate{ + PubKey: pubkey, + RewardAddress: rewardAddress, + OwnerAddress: ownerAddress, + Commission: commission, + Status: CandidateStatusOffline, + totalBipStake: big.NewInt(0), + stakes: [MaxDelegatorsPerCandidate]*Stake{}, + isDirty: true, + isTotalStakeDirty: true, + } + + candidate.setTmAddress() + c.setToMap(pubkey, candidate) +} + +func (c *Candidates) GetCandidateByTendermintAddress(address types.TmAddress) *Candidate { + candidates := c.GetCandidates() + for _, candidate := range candidates { + if candidate.GetTmAddress() == address { + return candidate + } + } + + return nil +} + +func (c *Candidates) Exists(pubkey types.Pubkey) bool { + c.lock.RLock() + defer c.lock.RUnlock() + + _, exists := c.list[pubkey] + + return exists +} + +func (c *Candidates) Count() int { + c.lock.RLock() + defer c.lock.RUnlock() + + return len(c.list) +} + +func (c *Candidates) GetCandidate(pubkey types.Pubkey) *Candidate { + return c.getFromMap(pubkey) +} + +func (c *Candidates) GetCandidates() []*Candidate { + var candidates []*Candidate + for _, pubkey := range c.getOrderedCandidates() { + candidates = append(candidates, c.getFromMap(pubkey)) + } + + return candidates +} + +func (c *Candidates) GetTotalStake(pubkey types.Pubkey) *big.Int { + candidate := c.getFromMap(pubkey) + if candidate.totalBipStake == nil { + path := []byte{mainPrefix} + path = append(path, pubkey[:]...) + path = append(path, totalStakePrefix) + _, enc := c.iavl.Get(path) + if len(enc) == 0 { + candidate.totalBipStake = big.NewInt(0) + return big.NewInt(0) + } + + candidate.totalBipStake = big.NewInt(0).SetBytes(enc) + } + + return candidate.totalBipStake +} + +func (c *Candidates) GetStakes(pubkey types.Pubkey) []*Stake { + candidate := c.GetCandidate(pubkey) + + var stakes []*Stake + for i := 0; i < MaxDelegatorsPerCandidate; i++ { + stake := candidate.stakes[i] + if stake == nil { + continue + } + stakes = append(stakes, stake) + } + + return stakes +} + +func (c *Candidates) StakesCount(pubkey types.Pubkey) int { + return c.GetCandidate(pubkey).stakesCount +} + +func (c *Candidates) GetStakeOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinSymbol) *Stake { + candidate := c.GetCandidate(pubkey) + for _, stake := range candidate.stakes { + if stake == nil { + continue + } + + if stake.Owner == address && stake.Coin == coin { + return stake + } + } + + return nil +} + +func (c *Candidates) GetStakeValueOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinSymbol) *big.Int { + stake := c.GetStakeOfAddress(pubkey, address, coin) + if stake == nil { + return nil + } + + return stake.Value +} + +func (c *Candidates) GetCandidateOwner(pubkey types.Pubkey) types.Address { + return c.getFromMap(pubkey).OwnerAddress +} + +func (c *Candidates) LoadCandidates() { + if c.loaded { + return + } + c.loaded = true + + path := []byte{mainPrefix} + _, enc := c.iavl.Get(path) + if len(enc) == 0 { + c.list = map[types.Pubkey]*Candidate{} + return + } + + var candidates []*Candidate + if err := rlp.DecodeBytes(enc, &candidates); err != nil { + panic(fmt.Sprintf("failed to decode candidates: %s", err)) + } + + c.list = map[types.Pubkey]*Candidate{} + for _, candidate := range candidates { + // load total stake + path = append([]byte{mainPrefix}, candidate.PubKey.Bytes()...) + path = append(path, totalStakePrefix) + _, enc = c.iavl.Get(path) + if len(enc) == 0 { + candidate.totalBipStake = big.NewInt(0) + } else { + candidate.totalBipStake = big.NewInt(0).SetBytes(enc) + } + + candidate.setTmAddress() + c.setToMap(candidate.PubKey, candidate) + } +} + +func (c *Candidates) LoadStakes() { + for pubkey := range c.list { + c.LoadStakesOfCandidate(pubkey) + } +} + +func (c *Candidates) Export(state *types.AppState, coinsMap map[types.CoinSymbol]types.Coin) map[types.Pubkey]uint32 { + c.LoadCandidates() + c.LoadStakes() + + candidatesMap := make(map[types.Pubkey]uint32) + + candidates, maxID := c.GetCandidates(), uint32(1) + for _, candidate := range candidates { + candidateStakes := c.GetStakes(candidate.PubKey) + stakes := make([]types.Stake, len(candidateStakes)) + for i, s := range candidateStakes { + stakes[i] = types.Stake{ + Owner: s.Owner, + Coin: coinsMap[s.Coin].ID, + Value: s.Value.String(), + BipValue: s.BipValue.String(), + } + } + + updates := make([]types.Stake, len(candidate.updates)) + for i, u := range candidate.updates { + updates[i] = types.Stake{ + Owner: u.Owner, + Coin: coinsMap[u.Coin].ID, + Value: u.Value.String(), + BipValue: u.BipValue.String(), + } + } + + state.Candidates = append(state.Candidates, types.Candidate{ + ID: uint64(maxID), + RewardAddress: candidate.RewardAddress, + OwnerAddress: candidate.OwnerAddress, + ControlAddress: candidate.OwnerAddress, + TotalBipStake: candidate.GetTotalBipStake().String(), + PubKey: candidate.PubKey, + Commission: uint64(candidate.Commission), + Status: uint64(candidate.Status), + Updates: updates, + Stakes: stakes, + }) + candidatesMap[candidate.PubKey] = maxID + + maxID++ + } + + return candidatesMap +} + +func (c *Candidates) getOrderedCandidates() []types.Pubkey { + c.lock.RLock() + defer c.lock.RUnlock() + + var keys []types.Pubkey + for _, candidate := range c.list { + keys = append(keys, candidate.PubKey) + } + + sort.SliceStable(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} + +func (c *Candidates) getFromMap(pubkey types.Pubkey) *Candidate { + c.lock.RLock() + defer c.lock.RUnlock() + + return c.list[pubkey] +} + +func (c *Candidates) setToMap(pubkey types.Pubkey, model *Candidate) { + c.lock.Lock() + defer c.lock.Unlock() + + c.list[pubkey] = model +} + +func (c *Candidates) SetTotalStake(pubkey types.Pubkey, stake *big.Int) { + c.GetCandidate(pubkey).setTotalBipStake(stake) +} + +func (c *Candidates) LoadStakesOfCandidate(pubkey types.Pubkey) { + candidate := c.GetCandidate(pubkey) + + // load stakes + stakesCount := 0 + for index := 0; index < MaxDelegatorsPerCandidate; index++ { + path := []byte{mainPrefix} + path = append(path, candidate.PubKey.Bytes()...) + path = append(path, stakesPrefix) + path = append(path, []byte(fmt.Sprintf("%d", index))...) + _, enc := c.iavl.Get(path) + if len(enc) == 0 { + candidate.stakes[index] = nil + continue + } + + stake := &Stake{} + if err := rlp.DecodeBytes(enc, stake); err != nil { + panic(fmt.Sprintf("failed to decode stake: %s", err)) + } + + candidate.SetStakeAtIndex(index, stake, false) + + stakesCount++ + } + + candidate.stakesCount = stakesCount + + // load updates + path := []byte{mainPrefix} + path = append(path, candidate.PubKey.Bytes()...) + path = append(path, updatesPrefix) + _, enc := c.iavl.Get(path) + if len(enc) == 0 { + candidate.updates = nil + } else { + var updates []*Stake + if err := rlp.DecodeBytes(enc, &updates); err != nil { + panic(fmt.Sprintf("failed to decode updated: %s", err)) + } + + for _, update := range updates { + update.markDirty = (func(candidate *Candidate) func(int) { + return func(i int) { + candidate.isUpdatesDirty = true + } + })(candidate) + } + + candidate.updates = updates + } + + // load total stake + path = append([]byte{mainPrefix}, candidate.PubKey.Bytes()...) + path = append(path, totalStakePrefix) + _, enc = c.iavl.Get(path) + if len(enc) == 0 { + candidate.totalBipStake = big.NewInt(0) + } else { + candidate.totalBipStake = big.NewInt(0).SetBytes(enc) + } + + candidate.setTmAddress() + c.setToMap(candidate.PubKey, candidate) +} diff --git a/legacy/candidates/model.go b/legacy/candidates/model.go new file mode 100644 index 000000000..10c4395bf --- /dev/null +++ b/legacy/candidates/model.go @@ -0,0 +1,251 @@ +package candidates + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/tendermint/tendermint/crypto/ed25519" + "math/big" + "sort" +) + +type Candidate struct { + PubKey types.Pubkey + RewardAddress types.Address + OwnerAddress types.Address + Commission uint32 + Status byte + + totalBipStake *big.Int + stakesCount int + stakes [MaxDelegatorsPerCandidate]*Stake + updates []*Stake + tmAddress *types.TmAddress + + isDirty bool + isTotalStakeDirty bool + isUpdatesDirty bool + dirtyStakes [MaxDelegatorsPerCandidate]bool +} + +func (candidate *Candidate) setStatus(status byte) { + candidate.isDirty = true + candidate.Status = status +} + +func (candidate *Candidate) setOwner(address types.Address) { + candidate.isDirty = true + candidate.OwnerAddress = address +} + +func (candidate *Candidate) setReward(address types.Address) { + candidate.isDirty = true + candidate.RewardAddress = address +} + +func (candidate *Candidate) addUpdate(stake *Stake) { + candidate.isUpdatesDirty = true + stake.markDirty = func(i int) { + candidate.isUpdatesDirty = true + } + candidate.updates = append(candidate.updates, stake) +} + +func (candidate *Candidate) clearUpdates() { + if len(candidate.updates) != 0 { + candidate.isUpdatesDirty = true + } + + candidate.updates = nil +} + +func (candidate *Candidate) setTotalBipStake(totalBipValue *big.Int) { + if totalBipValue.Cmp(candidate.totalBipStake) != 0 { + candidate.isTotalStakeDirty = true + } + + candidate.totalBipStake.Set(totalBipValue) +} + +func (candidate *Candidate) GetTmAddress() types.TmAddress { + return *candidate.tmAddress +} + +func (candidate *Candidate) setTmAddress() { + // set tm address + var pubkey ed25519.PubKeyEd25519 + copy(pubkey[:], candidate.PubKey[:]) + + var address types.TmAddress + copy(address[:], pubkey.Address().Bytes()) + + candidate.tmAddress = &address +} + +func (candidate *Candidate) GetFilteredUpdates() []*Stake { + var updates []*Stake + for _, update := range candidate.updates { + // skip updates with 0 stakes + if update.Value.Cmp(big.NewInt(0)) != 1 { + continue + } + + // merge updates + merged := false + for _, u := range updates { + if u.Coin == update.Coin && u.Owner == update.Owner { + u.Value.Add(u.Value, update.Value) + merged = true + break + } + } + + if !merged { + updates = append(updates, update) + } + } + + return updates +} + +func (candidate *Candidate) FilterUpdates() { + var updates []*Stake + for _, update := range candidate.updates { + // skip updates with 0 stakes + if update.Value.Cmp(big.NewInt(0)) != 1 { + continue + } + + // merge updates + merged := false + for _, u := range updates { + if u.Coin == update.Coin && u.Owner == update.Owner { + u.Value.Add(u.Value, update.Value) + merged = true + break + } + } + + if !merged { + updates = append(updates, update) + } + } + + sort.SliceStable(updates, func(i, j int) bool { + return updates[i].BipValue.Cmp(updates[j].BipValue) == 1 + }) + + candidate.updates = updates + candidate.isUpdatesDirty = true +} + +func (candidate *Candidate) updateStakesCount() { + count := 0 + for _, stake := range candidate.stakes { + if stake != nil { + count++ + } + } + candidate.stakesCount = count +} + +func (candidate *Candidate) GetTotalBipStake() *big.Int { + return big.NewInt(0).Set(candidate.totalBipStake) +} + +func (candidate *Candidate) SetStakeAtIndex(index int, stake *Stake, isDirty bool) { + stake.markDirty = func(i int) { + candidate.dirtyStakes[i] = true + } + stake.index = index + + candidate.stakes[index] = stake + + if isDirty { + stake.markDirty(index) + } +} + +type Stake struct { + Owner types.Address + Coin types.CoinSymbol + Value *big.Int + BipValue *big.Int + + index int + markDirty func(int) +} + +func (stake *Stake) addValue(value *big.Int) { + stake.markDirty(stake.index) + stake.Value.Add(stake.Value, value) +} + +func (stake *Stake) subValue(value *big.Int) { + stake.markDirty(stake.index) + stake.Value.Sub(stake.Value, value) +} + +func (stake *Stake) setBipValue(value *big.Int) { + if stake.BipValue.Cmp(value) != 0 { + stake.markDirty(stake.index) + } + + stake.BipValue.Set(value) +} + +func (stake *Stake) setNewOwner(coin types.CoinSymbol, owner types.Address) { + stake.Coin = coin + stake.Owner = owner + stake.BipValue = big.NewInt(0) + stake.Value = big.NewInt(0) + stake.markDirty(stake.index) +} + +func (stake *Stake) setValue(ret *big.Int) { + stake.markDirty(stake.index) + stake.Value.Set(ret) +} + +func (stake *Stake) setCoin(coin types.CoinSymbol) { + stake.markDirty(stake.index) + stake.Coin = coin +} + +type coinsCache struct { + list map[types.CoinSymbol]*coinsCacheItem +} + +func newCoinsCache() *coinsCache { + return &coinsCache{list: map[types.CoinSymbol]*coinsCacheItem{}} +} + +type coinsCacheItem struct { + totalPower *big.Int + totalAmount *big.Int +} + +func (c *coinsCache) Exists(symbol types.CoinSymbol) bool { + if c == nil { + return false + } + + _, exists := c.list[symbol] + + return exists +} + +func (c *coinsCache) Get(symbol types.CoinSymbol) (totalPower *big.Int, totalAmount *big.Int) { + return c.list[symbol].totalPower, c.list[symbol].totalAmount +} + +func (c *coinsCache) Set(symbol types.CoinSymbol, totalPower *big.Int, totalAmount *big.Int) { + if c == nil { + return + } + + if c.list[symbol] == nil { + c.list[symbol] = &coinsCacheItem{} + } + + c.list[symbol].totalAmount = totalAmount + c.list[symbol].totalPower = totalPower +} diff --git a/legacy/coins/coins.go b/legacy/coins/coins.go new file mode 100644 index 000000000..08638f44b --- /dev/null +++ b/legacy/coins/coins.go @@ -0,0 +1,200 @@ +package coins + +import ( + "bytes" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "math/big" + "sort" + "sync" +) + +const ( + mainPrefix = byte('q') + infoPrefix = byte('i') +) + +type Coins struct { + list map[types.CoinSymbol]*Model + dirty map[types.CoinSymbol]struct{} + + bus *bus.Bus + iavl tree.MTree + + lock sync.RWMutex +} + +func NewCoins(stateBus *bus.Bus, iavl tree.MTree) (*Coins, error) { + coins := &Coins{bus: stateBus, iavl: iavl, list: map[types.CoinSymbol]*Model{}, dirty: map[types.CoinSymbol]struct{}{}} + + return coins, nil +} + +func (c *Coins) GetCoin(symbol types.CoinSymbol) *Model { + return c.get(symbol) +} + +func (c *Coins) Exists(symbol types.CoinSymbol) bool { + if symbol.IsBaseCoin() { + return true + } + + return c.get(symbol) != nil +} + +func (c *Coins) get(symbol types.CoinSymbol) *Model { + if coin := c.getFromMap(symbol); coin != nil { + return coin + } + + path := []byte{mainPrefix} + path = append(path, symbol[:]...) + _, enc := c.iavl.Get(path) + if len(enc) == 0 { + return nil + } + + coin := &Model{} + if err := rlp.DecodeBytes(enc, coin); err != nil { + panic(fmt.Sprintf("failed to decode coin at %s: %s", symbol.String(), err)) + } + + coin.symbol = symbol + coin.markDirty = c.markDirty + + // load info + path = []byte{mainPrefix} + path = append(path, symbol[:]...) + path = append(path, infoPrefix) + _, enc = c.iavl.Get(path) + if len(enc) != 0 { + var info Info + if err := rlp.DecodeBytes(enc, &info); err != nil { + panic(fmt.Sprintf("failed to decode coin info %s: %s", symbol.String(), err)) + } + + coin.info = &info + } + + c.setToMap(symbol, coin) + + return coin +} + +func (c *Coins) markDirty(symbol types.CoinSymbol) { + c.dirty[symbol] = struct{}{} +} + +func (c *Coins) getOrderedDirtyCoins() []types.CoinSymbol { + keys := make([]types.CoinSymbol, 0, len(c.dirty)) + for k := range c.dirty { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return bytes.Compare(keys[i].Bytes(), keys[j].Bytes()) == 1 + }) + + return keys +} + +func (c *Coins) Export(state *types.AppState) map[types.CoinSymbol]types.Coin { + var coins []types.Coin + + if len(state.Coins) != 0 { + for k, coin := range state.Coins { + // check coins' volume + volume := big.NewInt(0) + for _, ff := range state.FrozenFunds { + if ff.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(ff.Value)) + } + } + + for _, candidate := range state.Candidates { + for _, stake := range candidate.Stakes { + if stake.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(stake.Value)) + } + } + + for _, stake := range candidate.Updates { + if stake.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(stake.Value)) + } + } + } + + for _, account := range state.Accounts { + for _, bal := range account.Balance { + if bal.Coin == coin.ID { + volume.Add(volume, helpers.StringToBigInt(bal.Value)) + } + } + } + + state.Coins[k].Volume = volume.String() + } + + return nil + } + + c.iavl.Iterate(func(key []byte, value []byte) bool { + if key[0] == mainPrefix { + if len(key[1:]) > types.CoinSymbolLength { + return false + } + + coinSymbol := types.StrToCoinSymbol(string(key[1:])) + coin := c.GetCoin(coinSymbol) + + coinModel := types.Coin{ + Name: coin.Name(), + Symbol: coin.Symbol(), + Volume: coin.Volume().String(), + Crr: uint64(coin.Crr()), + Reserve: coin.Reserve().String(), + MaxSupply: coin.MaxSupply().String(), + Version: 0, + } + + coins = append(coins, coinModel) + } + + return false + }) + + sort.Slice(coins[:], func(i, j int) bool { + return helpers.StringToBigInt(coins[i].Reserve).Cmp(helpers.StringToBigInt(coins[j].Reserve)) == 1 + }) + + coinsMap := make(map[types.CoinSymbol]types.Coin, len(coins)) + coinsMap[types.GetBaseCoin()] = types.Coin{ID: uint64(types.GetBaseCoinID())} + + for i := range coins { + coins[i].ID = uint64(i + 1) + coinsMap[coins[i].Symbol] = coins[i] + } + + state.Coins = coins + + return coinsMap +} + +func (c *Coins) getFromMap(symbol types.CoinSymbol) *Model { + c.lock.RLock() + defer c.lock.RUnlock() + + return c.list[symbol] +} + +func (c *Coins) setToMap(symbol types.CoinSymbol, model *Model) { + c.lock.Lock() + defer c.lock.Unlock() + + c.list[symbol] = model +} diff --git a/legacy/coins/model.go b/legacy/coins/model.go new file mode 100644 index 000000000..262fc1146 --- /dev/null +++ b/legacy/coins/model.go @@ -0,0 +1,107 @@ +package coins + +import ( + "fmt" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "math/big" +) + +var minCoinReserve = helpers.BipToPip(big.NewInt(10000)) + +type Model struct { + CName string + CCrr uint + CMaxSupply *big.Int + + symbol types.CoinSymbol + info *Info + markDirty func(symbol types.CoinSymbol) + isDirty bool +} + +func (m Model) Name() string { + return m.CName +} + +func (m Model) Symbol() types.CoinSymbol { + return m.symbol +} + +func (m Model) Crr() uint { + return m.CCrr +} + +func (m Model) Volume() *big.Int { + return big.NewInt(0).Set(m.info.Volume) +} + +func (m Model) Reserve() *big.Int { + return big.NewInt(0).Set(m.info.Reserve) +} + +func (m *Model) SubVolume(amount *big.Int) { + m.info.Volume.Sub(m.info.Volume, amount) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) AddVolume(amount *big.Int) { + m.info.Volume.Add(m.info.Volume, amount) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) SubReserve(amount *big.Int) { + m.info.Reserve.Sub(m.info.Reserve, amount) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) AddReserve(amount *big.Int) { + m.info.Reserve.Add(m.info.Reserve, amount) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) SetReserve(reserve *big.Int) { + m.info.Reserve.Set(reserve) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) SetVolume(volume *big.Int) { + m.info.Volume.Set(volume) + m.markDirty(m.symbol) + m.info.isDirty = true +} + +func (m *Model) CheckReserveUnderflow(delta *big.Int) error { + total := big.NewInt(0).Sub(m.Reserve(), delta) + + if total.Cmp(minCoinReserve) == -1 { + min := big.NewInt(0).Add(minCoinReserve, delta) + return fmt.Errorf("coin %s reserve is too small (%s, required at least %s)", m.symbol.String(), m.Reserve().String(), min.String()) + } + + return nil +} + +func (m Model) IsInfoDirty() bool { + return m.info.isDirty +} + +func (m Model) IsDirty() bool { + return m.isDirty +} + +func (m Model) MaxSupply() *big.Int { + return m.CMaxSupply +} + +type Info struct { + Volume *big.Int + Reserve *big.Int + + isDirty bool +} diff --git a/legacy/frozenfunds/frozen_funds.go b/legacy/frozenfunds/frozen_funds.go new file mode 100644 index 000000000..d184c789c --- /dev/null +++ b/legacy/frozenfunds/frozen_funds.go @@ -0,0 +1,115 @@ +package frozenfunds + +import ( + "encoding/binary" + "fmt" + "github.com/MinterTeam/minter-go-node/core/state/bus" + "github.com/MinterTeam/minter-go-node/core/state/candidates" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/tree" + "sort" + "sync" +) + +const mainPrefix = byte('f') + +type RFrozenFunds interface { + Export(state *types.AppState, height uint64) +} + +type FrozenFunds struct { + list map[uint64]*Model + dirty map[uint64]interface{} + + bus *bus.Bus + iavl tree.MTree + + lock sync.RWMutex +} + +func NewFrozenFunds(stateBus *bus.Bus, iavl tree.MTree) (*FrozenFunds, error) { + frozenfunds := &FrozenFunds{bus: stateBus, iavl: iavl, list: map[uint64]*Model{}, dirty: map[uint64]interface{}{}} + return frozenfunds, nil +} + +func (f *FrozenFunds) get(height uint64) *Model { + if ff := f.getFromMap(height); ff != nil { + return ff + } + + _, enc := f.iavl.Get(getPath(height)) + if len(enc) == 0 { + return nil + } + + ff := &Model{} + if err := rlp.DecodeBytes(enc, ff); err != nil { + panic(fmt.Sprintf("failed to decode frozen funds at height %d: %s", height, err)) + } + + ff.height = height + ff.markDirty = f.markDirty + + f.setToMap(height, ff) + + return ff +} + +func (f *FrozenFunds) markDirty(height uint64) { + f.dirty[height] = struct{}{} +} + +func (f *FrozenFunds) getOrderedDirty() []uint64 { + keys := make([]uint64, 0, len(f.dirty)) + for k := range f.dirty { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + return keys +} + +func (f *FrozenFunds) Export(state *types.AppState, height uint64, coinsMap map[types.CoinSymbol]types.Coin, candidatesMap map[types.Pubkey]uint32) { + for i := height; i <= height+candidates.UnbondPeriod; i++ { + frozenFunds := f.get(i) + if frozenFunds == nil { + continue + } + + for _, frozenFund := range frozenFunds.List { + state.FrozenFunds = append(state.FrozenFunds, types.FrozenFund{ + Height: i - height, + Address: frozenFund.Address, + CandidateKey: frozenFund.CandidateKey, + CandidateID: uint64(candidatesMap[*frozenFund.CandidateKey]), + Coin: coinsMap[frozenFund.Coin].ID, + Value: frozenFund.Value.String(), + }) + } + } +} + +func (f *FrozenFunds) getFromMap(height uint64) *Model { + f.lock.RLock() + defer f.lock.RUnlock() + + return f.list[height] +} + +func (f *FrozenFunds) setToMap(height uint64, model *Model) { + f.lock.Lock() + defer f.lock.Unlock() + + f.list[height] = model +} + +func getPath(height uint64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, height) + + return append([]byte{mainPrefix}, b...) +} diff --git a/legacy/frozenfunds/model.go b/legacy/frozenfunds/model.go new file mode 100644 index 000000000..826effb1a --- /dev/null +++ b/legacy/frozenfunds/model.go @@ -0,0 +1,40 @@ +package frozenfunds + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" +) + +type Item struct { + Address types.Address + CandidateKey *types.Pubkey + Coin types.CoinSymbol + Value *big.Int +} + +type Model struct { + List []Item + + height uint64 + deleted bool + markDirty func(height uint64) +} + +func (m *Model) delete() { + m.deleted = true + m.markDirty(m.height) +} + +func (m *Model) addFund(address types.Address, pubkey types.Pubkey, coin types.CoinSymbol, value *big.Int) { + m.List = append(m.List, Item{ + Address: address, + CandidateKey: &pubkey, + Coin: coin, + Value: value, + }) + m.markDirty(m.height) +} + +func (m *Model) Height() uint64 { + return m.height +} diff --git a/log/log.go b/log/log.go index 5535e8f07..b5bd9410f 100644 --- a/log/log.go +++ b/log/log.go @@ -8,6 +8,7 @@ import ( "os" ) +// NewLogger returns a logger based on given config func NewLogger(cfg *config.Config) log.Logger { var dest io.Writer = os.Stdout diff --git a/math/big.go b/math/big.go index ae03c388e..9ab048290 100644 --- a/math/big.go +++ b/math/big.go @@ -176,6 +176,12 @@ func U256(x *big.Int) *big.Int { return x.And(x, tt256m1) } +// U256Bytes converts a big Int into a 256bit EVM number. +// This operation is destructive. +func U256Bytes(n *big.Int) []byte { + return PaddedBigBytes(U256(n), 32) +} + // S256 interprets x as a two's complement number. // x must not exceed 256 bits (the result is undefined if it does) and is not modified. // diff --git a/math/big_test.go b/math/big_test.go new file mode 100644 index 000000000..a7ee17aa2 --- /dev/null +++ b/math/big_test.go @@ -0,0 +1,326 @@ +// 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 ( + "bytes" + "encoding/hex" + "github.com/MinterTeam/minter-go-node/core/types" + "math/big" + "testing" +) + +func TestHexOrDecimal256(t *testing.T) { + tests := []struct { + input string + num *big.Int + ok bool + }{ + {"", big.NewInt(0), true}, + {"0", big.NewInt(0), true}, + {"0x0", big.NewInt(0), true}, + {"12345678", big.NewInt(12345678), true}, + {"0x12345678", big.NewInt(0x12345678), true}, + {"0X12345678", big.NewInt(0x12345678), true}, + // Tests for leading zero behaviour: + {"0123456789", big.NewInt(123456789), true}, // note: not octal + {"00", big.NewInt(0), true}, + {"0x00", big.NewInt(0), true}, + {"0x012345678abc", big.NewInt(0x12345678abc), true}, + // Invalid syntax: + {"abcdef", nil, false}, + {"0xgg", nil, false}, + // Larger than 256 bits: + {"115792089237316195423570985008687907853269984665640564039457584007913129639936", nil, false}, + } + for _, test := range tests { + var num HexOrDecimal256 + err := num.UnmarshalText([]byte(test.input)) + if (err == nil) != test.ok { + t.Errorf("ParseBig(%q) -> (err == nil) == %t, want %t", test.input, err == nil, test.ok) + continue + } + if test.num != nil && (*big.Int)(&num).Cmp(test.num) != 0 { + t.Errorf("ParseBig(%q) -> %d, want %d", test.input, (*big.Int)(&num), test.num) + } + } +} + +func TestMustParseBig256(t *testing.T) { + defer func() { + if recover() == nil { + t.Error("MustParseBig should've panicked") + } + }() + MustParseBig256("ggg") +} + +func TestBigMax(t *testing.T) { + a := big.NewInt(10) + b := big.NewInt(5) + + max1 := BigMax(a, b) + if max1 != a { + t.Errorf("Expected %d got %d", a, max1) + } + + max2 := BigMax(b, a) + if max2 != a { + t.Errorf("Expected %d got %d", a, max2) + } +} + +func TestBigMin(t *testing.T) { + a := big.NewInt(10) + b := big.NewInt(5) + + min1 := BigMin(a, b) + if min1 != b { + t.Errorf("Expected %d got %d", b, min1) + } + + min2 := BigMin(b, a) + if min2 != b { + t.Errorf("Expected %d got %d", b, min2) + } +} + +func TestFirstBigSet(t *testing.T) { + tests := []struct { + num *big.Int + ix int + }{ + {big.NewInt(0), 0}, + {big.NewInt(1), 0}, + {big.NewInt(2), 1}, + {big.NewInt(0x100), 8}, + } + for _, test := range tests { + if ix := FirstBitSet(test.num); ix != test.ix { + t.Errorf("FirstBitSet(b%b) = %d, want %d", test.num, ix, test.ix) + } + } +} + +func TestPaddedBigBytes(t *testing.T) { + tests := []struct { + num *big.Int + n int + result []byte + }{ + {num: big.NewInt(0), n: 4, result: []byte{0, 0, 0, 0}}, + {num: big.NewInt(1), n: 4, result: []byte{0, 0, 0, 1}}, + {num: big.NewInt(512), n: 4, result: []byte{0, 0, 2, 0}}, + {num: BigPow(2, 32), n: 4, result: []byte{1, 0, 0, 0, 0}}, + } + for _, test := range tests { + if result := PaddedBigBytes(test.num, test.n); !bytes.Equal(result, test.result) { + t.Errorf("PaddedBigBytes(%d, %d) = %v, want %v", test.num, test.n, result, test.result) + } + } +} + +func BenchmarkPaddedBigBytesLargePadding(b *testing.B) { + bigint := MustParseBig256("123456789123456789123456789123456789") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 200) + } +} + +func BenchmarkPaddedBigBytesSmallPadding(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 5) + } +} + +func BenchmarkPaddedBigBytesSmallOnePadding(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 32) + } +} + +func BenchmarkByteAtBrandNew(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + bigEndianByteAt(bigint, 15) + } +} + +func BenchmarkByteAt(b *testing.B) { + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + bigEndianByteAt(bigint, 15) + } +} + +func BenchmarkByteAtOld(b *testing.B) { + + bigint := MustParseBig256("0x18F8F8F1000111000110011100222004330052300000000000000000FEFCF3CC") + for i := 0; i < b.N; i++ { + PaddedBigBytes(bigint, 32) + } +} + +func TestReadBits(t *testing.T) { + check := func(input string) { + want, _ := hex.DecodeString(input) + int, _ := new(big.Int).SetString(input, 16) + buf := make([]byte, len(want)) + ReadBits(int, buf) + if !bytes.Equal(buf, want) { + t.Errorf("have: %x\nwant: %x", buf, want) + } + } + check("000000000000000000000000000000000000000000000000000000FEFCF3F8F0") + check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0") + check("18F8F8F1000111000110011100222004330052300000000000000000FEFCF3F8F0") +} + +func TestU256(t *testing.T) { + tests := []struct{ x, y *big.Int }{ + {x: big.NewInt(0), y: big.NewInt(0)}, + {x: big.NewInt(1), y: big.NewInt(1)}, + {x: BigPow(2, 255), y: BigPow(2, 255)}, + {x: BigPow(2, 256), y: big.NewInt(0)}, + {x: new(big.Int).Add(BigPow(2, 256), big.NewInt(1)), y: big.NewInt(1)}, + // negative values + {x: big.NewInt(-1), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1))}, + {x: big.NewInt(-2), y: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2))}, + {x: BigPow(2, -255), y: big.NewInt(1)}, + } + for _, test := range tests { + if y := U256(new(big.Int).Set(test.x)); y.Cmp(test.y) != 0 { + t.Errorf("U256(%x) = %x, want %x", test.x, y, test.y) + } + } +} + +func TestU256Bytes(t *testing.T) { + ubytes := make([]byte, 32) + ubytes[31] = 1 + + unsigned := U256Bytes(big.NewInt(1)) + if !bytes.Equal(unsigned, ubytes) { + t.Errorf("expected %x got %x", ubytes, unsigned) + } +} + +func TestBigEndianByteAt(t *testing.T) { + tests := []struct { + x string + y int + exp byte + }{ + {"00", 0, 0x00}, + {"01", 1, 0x00}, + {"00", 1, 0x00}, + {"01", 0, 0x01}, + {"0000000000000000000000000000000000000000000000000000000000102030", 0, 0x30}, + {"0000000000000000000000000000000000000000000000000000000000102030", 1, 0x20}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 31, 0xAB}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 32, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 500, 0x00}, + } + for _, test := range tests { + v := new(big.Int).SetBytes(types.Hex2Bytes(test.x)) + actual := bigEndianByteAt(v, test.y) + if actual != test.exp { + t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) + } + + } +} +func TestLittleEndianByteAt(t *testing.T) { + tests := []struct { + x string + y int + exp byte + }{ + {"00", 0, 0x00}, + {"01", 1, 0x00}, + {"00", 1, 0x00}, + {"01", 0, 0x00}, + {"0000000000000000000000000000000000000000000000000000000000102030", 0, 0x00}, + {"0000000000000000000000000000000000000000000000000000000000102030", 1, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 31, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 32, 0x00}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 0, 0xAB}, + {"ABCDEF0908070605040302010000000000000000000000000000000000000000", 1, 0xCD}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 0, 0x00}, + {"00CDEF090807060504030201ffffffffffffffffffffffffffffffffffffffff", 1, 0xCD}, + {"0000000000000000000000000000000000000000000000000000000000102030", 31, 0x30}, + {"0000000000000000000000000000000000000000000000000000000000102030", 30, 0x20}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 32, 0x0}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 31, 0xFF}, + {"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 0xFFFF, 0x0}, + } + for _, test := range tests { + v := new(big.Int).SetBytes(types.Hex2Bytes(test.x)) + actual := Byte(v, 32, test.y) + if actual != test.exp { + t.Fatalf("Expected [%v] %v:th byte to be %v, was %v.", test.x, test.y, test.exp, actual) + } + + } +} + +func TestS256(t *testing.T) { + tests := []struct{ x, y *big.Int }{ + {x: big.NewInt(0), y: big.NewInt(0)}, + {x: big.NewInt(1), y: big.NewInt(1)}, + {x: big.NewInt(2), y: big.NewInt(2)}, + { + x: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), + y: new(big.Int).Sub(BigPow(2, 255), big.NewInt(1)), + }, + { + x: BigPow(2, 255), + y: new(big.Int).Neg(BigPow(2, 255)), + }, + { + x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(1)), + y: big.NewInt(-1), + }, + { + x: new(big.Int).Sub(BigPow(2, 256), big.NewInt(2)), + y: big.NewInt(-2), + }, + } + for _, test := range tests { + if y := S256(test.x); y.Cmp(test.y) != 0 { + t.Errorf("S256(%x) = %x, want %x", test.x, y, test.y) + } + } +} + +func TestExp(t *testing.T) { + tests := []struct{ base, exponent, result *big.Int }{ + {base: big.NewInt(0), exponent: big.NewInt(0), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(0), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(1), result: big.NewInt(1)}, + {base: big.NewInt(1), exponent: big.NewInt(2), result: big.NewInt(1)}, + {base: big.NewInt(3), exponent: big.NewInt(144), result: MustParseBig256("507528786056415600719754159741696356908742250191663887263627442114881")}, + {base: big.NewInt(2), exponent: big.NewInt(255), result: MustParseBig256("57896044618658097711785492504343953926634992332820282019728792003956564819968")}, + } + for _, test := range tests { + if result := Exp(test.base, test.exponent); result.Cmp(test.result) != 0 { + t.Errorf("Exp(%d, %d) = %d, want %d", test.base, test.exponent, result, test.result) + } + } +} diff --git a/math/exp_test.go b/math/exp_test.go new file mode 100644 index 000000000..0dc0308f5 --- /dev/null +++ b/math/exp_test.go @@ -0,0 +1,120 @@ +package math_test + +import ( + "fmt" + "math" + "math/big" + "math/rand" + "testing" + + bigfloat "github.com/MinterTeam/minter-go-node/math" +) + +func TestExp(t *testing.T) { + for _, test := range []struct { + z string + want string + }{ + {"0", "1"}, + {"1", "2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274274663919320030599218174135966290435729003342952605956307381323286279434907632338298807531952510190115738341879307021540891499348841675092447614606680822648001684774118537423454424371075390777449920695517027618386062613313845830007520449338265602976"}, + {"1.5", "4.4816890703380648226020554601192758190057498683696670567726500827859366744667137729810538313824533913886163506518301957689627464772204086069617596449736935381785298966216866555101351015468252930697652168582207145843214628870201383138594206299440366192845735003904511744769330306157776861818063974846024442278949433305735015582659896397844432731673273"}, + {"2", "7.3890560989306502272304274605750078131803155705518473240871278225225737960790577633843124850791217947737531612654788661238846036927812733744783922133980777749001228956074107537023913309475506820865818202696478682084042209822552348757424625414146799281293318880707633010193378997407299869600953033075153208188236846947930299135587714456831239232727646"}, + {"3", "20.085536923187667740928529654581717896987907838554150144378934229698845878091973731204497160253017702153607615851949002881811012479353506690232621784477250503945677100066077851812229047884383940258152534709352622981465538424555697733515108150118404754933838497843177676070913772862491787349396037822793717687131254060597553426640826030948663920216259"}, + + {"-1", "0.36787944117144232159552377016146086744581113103176783450783680169746149574489980335714727434591964374662732527684399520824697579279012900862665358949409878309219436737733811504863899112514561634498771997868447595793974730254989249545323936620796481051464752061229422308916492656660036507457728370553285373838810680478761195682989345449735073931859922"}, + {"-2", "0.13533528323661269189399949497248440340763154590957588146815887265407337410148768993709812249065704875507728718963355221244934687189285303815889513499670600559125022755868258230483842057584538468003599408344602481287135375015664353399593608501390049529421705857601948571122397095990883595090571764528251279379538022237440385068269131295459365886804367"}, + {"-3", "0.049787068367863942979342415650061776631699592188423215567627727606060667730199550154054244236633344526401328650893681950864643386736174297123488422626590132549710257089250891729183705544267766471294627261313755158051249249208013335774449487985072339959233419058693861230319791977014791562486437888837044087835498513120050395020976909328170160274676519"}, + + {"10", "22026.465794806716516957900645284244366353512618556781074235426355225202818570792575199120968164525895451555501092457836652423291606522895166222480137728972873485577837847275195480610095881417055888657927317236168401192698035170264925041101757502556764762696107543817931960834044404934236682455357614946828619042431465132389556031319229262768101604495"}, + {"100", "2.6881171418161354484126255515800135873611118773741922415191608615280287034909564914158871097219845710811670879190576068697597709761868233548459638929871966089629133626120029380957276534032962269865668016917743514451846065162804442237756762296960284731911402129862281040057911593878790384974173340084912432828126815454426051808828625966509400466909062e43"}, + {"1000", "1.9700711140170469938888793522433231253169379853238457899528029913850638507824411934749780765630268899309638179875202269359829817305446128992326278366015282523232053516958456675619227156760278807142246682631400685516850865349794166031604536781793809290529972858013286994585647028653437590045656435558915622042232026051882611228863835837224872472521451e434"}, + + {"-10", "0.000045399929762484851535591515560550610237918088866564969259071305650999421614302281652525004545947782321708055089686028492945199117244520388837183347709414567560990909217007363970181059501783900762968517787030908824365171548448722293652332416020501168264360305604941570107729975354408079403994232932138270780520042710498960354486166066837009201707573209"}, + {"-100", "3.7200759760208359629596958038631183373588922923767819671206138766632904758958157181571187786422814966019356176423110698002479856420525356002661856882839075574388191160228448691497585855102816611741608772370701345082175755257496876380478927279529400619796226477050521097935092405571614981699373980650794385017392666116669084820355852767349264735965334e-44"}, + {"-1000", "5.0759588975494567652918094795743369193055992828928373618323938454105405429748191756796621690465428678636671068310652851135787934480190632251259072300213915638091771495398351108574919194309548129952421441572726108465407163812260104924530270737073247546217081943180823516857873407345613076984468096760005536701904004361380296144254899617340297251706670e-435"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + z := new(big.Float).SetPrec(prec) + z.Parse(test.z, 10) + + x := bigfloat.ExpFloat(z) + + if x.Cmp(want) != 0 { + t.Errorf("prec = %d, Exp(%v) =\ngot %g;\nwant %g", prec, test.z, x, want) + } + } + } +} + +func testExpFloat64(scale float64, nTests int, t *testing.T) { + for i := 0; i < nTests; i++ { + r := rand.Float64() * scale + + z := big.NewFloat(r) + z64, acc := bigfloat.ExpFloat(z).Float64() + + want := math.Exp(r) + + // Unfortunately, the Go math.Exp function is not completely + // accurate, so it doesn't make sense to require 100% + // compatibility with it, since it happens that math.Exp + // returns a result with the last bit off (same as math.Log). + // + // Just require a relative error smaller than 1e-14. + if math.Abs(z64-want)/want > 1e-14 || acc != big.Exact { + t.Errorf("Exp(%g) =\n got %g (%s);\nwant %g (Exact)", z, z64, acc, want) + } + } +} + +func TestExpFloat64Small(t *testing.T) { + testExpFloat64(-100, 4e3, t) + testExpFloat64(-10, 4e3, t) + testExpFloat64(-1, 4e3, t) +} + +func TestExpFloat64Medium(t *testing.T) { + testExpFloat64(0.1, 5e3, t) + testExpFloat64(1, 5e3, t) +} + +func TestExpFloat64Big(t *testing.T) { + testExpFloat64(10, 5e3, t) + testExpFloat64(100, 5e3, t) +} + +func TestExpSpecialValues(t *testing.T) { + for _, f := range []float64{ + +0.0, + -0.0, + math.Inf(+1), + math.Inf(-1), + } { + z := big.NewFloat(f) + x64, acc := bigfloat.ExpFloat(z).Float64() + want := math.Exp(f) + if x64 != want || acc != big.Exact { + t.Errorf("Log(%f) =\n got %g (%s);\nwant %g (Exact)", f, x64, acc, want) + } + } +} + +// ---------- Benchmarks ---------- + +func BenchmarkExp(b *testing.B) { + z := big.NewFloat(2).SetPrec(1e5) + _ = bigfloat.ExpFloat(z) // fill pi cache before benchmarking + + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + z = big.NewFloat(2).SetPrec(prec) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + bigfloat.ExpFloat(z) + } + }) + } +} diff --git a/math/log_test.go b/math/log_test.go new file mode 100644 index 000000000..b0c9ccb0b --- /dev/null +++ b/math/log_test.go @@ -0,0 +1,114 @@ +package math_test + +import ( + "fmt" + "math" + "math/big" + "math/rand" + "testing" + + bigfloat "github.com/MinterTeam/minter-go-node/math" +) + +// See note in sqrt_test.go about which numbers +// can we safely test this way. + +func TestLog(t *testing.T) { + for _, test := range []struct { + z string + want string + }{ + // 350 decimal digits are enough to give us up to 1000 binary digits + {"0.5", "-0.69314718055994530941723212145817656807550013436025525412068000949339362196969471560586332699641868754200148102057068573368552023575813055703267075163507596193072757082837143519030703862389167347112335011536449795523912047517268157493206515552473413952588295045300709532636664265410423915781495204374043038550080194417064167151864471283996817178454696"}, + {"0.25", "-1.3862943611198906188344642429163531361510002687205105082413600189867872439393894312117266539928373750840029620411413714673710404715162611140653415032701519238614551416567428703806140772477833469422467002307289959104782409503453631498641303110494682790517659009060141906527332853082084783156299040874808607710016038883412833430372894256799363435690939"}, + {"0.0125", "-4.3820266346738816122696878190588939118276018917095387383953679294477534755864366270535871860788543609679722271039983058344660861723571984277642996240040095752750899208106689864147210106979082189417635556550588715983462075888842670124944153533207460860520530946333864410280342429017041970928492563533263928706772062013203262792640026952942261381891629"}, + + {"1", "0.0"}, + {"2", "0.69314718055994530941723212145817656807550013436025525412068000949339362196969471560586332699641868754200148102057068573368552023575813055703267075163507596193072757082837143519030703862389167347112335011536449795523912047517268157493206515552473413952588295045300709532636664265410423915781495204374043038550080194417064167151864471283996817178454696"}, + {"10", "2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862486334095254650828067566662873690987816894829072083255546808437998948262331985283935053089653777326288461633662222876982198867465436674744042432743651550489343149393914796194044002221051017141748003688084012647080685567743216228355220"}, + {"4096", "8.3177661667193437130067854574981188169060016123230630494481601139207234636363365872703599239570242505040177722468482288042262428290975666843920490196209115431687308499404572222836844634867000816534802013843739754628694457020721788991847818662968096743105954054360851439163997118492508698937794245248851646260096233300477000582237365540796180614145634"}, + {"1e5", "11.512925464970228420089957273421821038005507443143864880166639504837863048386762401179986025447991491709838920211431243167047627325414033783331436845493908447414536041627773404218999474131165992641967526544826888663144230816831111438491099433732718337372021216371825775244671574696957398097022001110525508570874001844042006323540342783871608114177610"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + z := new(big.Float).SetPrec(prec) + z.Parse(test.z, 10) + + x := bigfloat.Log(z) + + if x.Cmp(want) != 0 { + t.Errorf("prec = %d, Log(%v) =\ngot %g;\n want %g", prec, test.z, x, want) + } + } + } +} + +func testLogFloat64(scale float64, nTests int, t *testing.T) { + for i := 0; i < nTests; i++ { + r := rand.Float64() * scale + + z := big.NewFloat(r) + x64, acc := bigfloat.Log(z).Float64() + + want := math.Log(r) + + // Unfortunately, the Go math.Log function is not completely + // accurate, so it doesn't make sense to require 100% + // compatibility with it, since it happens that math.Log + // returns a result with the last bit off (see Issue #9546). + // + // Just require a relative error smaller than 1e-14. + if math.Abs(x64-want)/want > 1e-14 || acc != big.Exact { + t.Errorf("Log(%g) =\n got %g (%s);\nwant %g (Exact)", z, x64, acc, want) + } + } +} + +func TestLogFloat64Small(t *testing.T) { + testLogFloat64(1e-100, 1e4, t) + testLogFloat64(1e-10, 1e4, t) +} + +func TestLogFloat64Medium(t *testing.T) { + testLogFloat64(1, 1e4, t) + testLogFloat64(100, 1e4, t) +} + +func TestLogFloat64Big(t *testing.T) { + testLogFloat64(1e10, 1e4, t) + testLogFloat64(1e100, 1e4, t) +} + +func TestLogSpecialValues(t *testing.T) { + for _, f := range []float64{ + +0.0, + -0.0, + math.Inf(+1), + } { + z := big.NewFloat(f) + x64, acc := bigfloat.Log(z).Float64() + want := math.Log(f) + if x64 != want || acc != big.Exact { + t.Errorf("Log(%f) =\n got %g (%s);\nwant %g (Exact)", f, x64, acc, want) + } + } +} + +// ---------- Benchmarks ---------- + +func BenchmarkLog(b *testing.B) { + z := big.NewFloat(2).SetPrec(1e5) + _ = bigfloat.Log(z) // fill pi cache before benchmarking + + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + z = big.NewFloat(2).SetPrec(prec) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + bigfloat.Log(z) + } + }) + } +} diff --git a/math/misc_test.go b/math/misc_test.go new file mode 100644 index 000000000..9345b431b --- /dev/null +++ b/math/misc_test.go @@ -0,0 +1,84 @@ +package math + +import ( + "fmt" + "math/big" + "testing" +) + +const maxPrec uint = 1100 + +func TestAgm(t *testing.T) { + for _, test := range []struct { + a, b string + want string + }{ + // 350 decimal digits are enough to give us up to 1000 binary digits + {"1", "2", "1.4567910310469068691864323832650819749738639432213055907941723832679264545802509002574737128184484443281894018160367999355762430743401245116912132499522793768970211976726893728266666782707432902072384564600963133367494416649516400826932239086263376738382410254887262645136590660408875885100466728130947439789355129117201754471869564160356411130706061"}, + {"1", "10", "4.2504070949322748617281643183731348667984678641901928596701476622237553127409037845252854607876171790458817135897668652366410690187825866854343005714304399718866701345600268795095037823053677248108795697049522041225723229732458947507697835936406527028150257238518982793084569470658500853106997941082919334694146843915361847332301248942222685517896377"}, + {"1", "0.125", "0.45196952219967034359164911331276507645541557018306954112635037493237190371123433961098897571407153216488726488616781446636283304514042965741376539315003644325377859387794608118242990700589889155408232061013871480906595147189700268152276449512798584772002737950386745259435790965051247641106770187776231088478906739003673011639874297764052324720923824"}, + {"1", "0.00390625", "0.2266172673264813935990249059047521131153183423554951008357647589399579243281007098800682366778894106068183449922373565084840603788091294841822891406755449218057751291845474188560350241555526734834267320629182988862200822134426714354129001630331838172767684623648755579758508073234772093745831056731263684472818466567279847347734121500617411676068370"}, + {"1", "0.0001220703125", "0.15107867088555894565277006051956059212554039802503247524478909254186086852737399490629222674071181480492157167137547694132610166031526264375084434300568336411139925857454913414480542768807718797335060713475211709310835676172131569048902323084439330888400622327072954342544508199547787750415198261456314278054748992781108231991187512975110547417178045"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + a := new(big.Float).SetPrec(prec) + a.Parse(test.a, 10) + + b := new(big.Float).SetPrec(prec) + b.Parse(test.b, 10) + + z := agm(a, b) + + if z.Cmp(want) != 0 { + t.Errorf("prec = %d, Agm(%v, %v) =\ngot %g;\nwant %g", prec, test.a, test.b, z, want) + } + } + } +} + +func TestPi(t *testing.T) { + enablePiCache = false + piStr := "3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153644" + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + + want := new(big.Float).SetPrec(prec) + want.Parse(piStr, 10) + + z := pi(prec) + + if z.Cmp(want) != 0 { + t.Errorf("Pi(%d) =\ngot %g;\nwant %g", prec, z, want) + } + } + enablePiCache = true +} + +// ---------- Benchmarks ---------- + +func BenchmarkAgm(b *testing.B) { + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + x := new(big.Float).SetPrec(prec).SetFloat64(1) + y := new(big.Float).SetPrec(prec).SetFloat64(0.125) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + agm(x, y) + } + }) + } +} + +func BenchmarkPi(b *testing.B) { + enablePiCache = false + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + pi(prec) + } + }) + } +} diff --git a/math/pow_test.go b/math/pow_test.go new file mode 100644 index 000000000..8dd7893ac --- /dev/null +++ b/math/pow_test.go @@ -0,0 +1,167 @@ +package math_test + +import ( + "fmt" + bigfloat "github.com/MinterTeam/minter-go-node/math" + "math" + "math/big" + "math/rand" + "testing" +) + +func TestPow(t *testing.T) { + for _, test := range []struct { + z, w string + want string + }{ + {"1.5", "1.5", "1.8371173070873835736479630560294185439744606104925025963245194254382202830929862699048945748284801761139459509199606418436441490948783180062193379634279589146216845606457574284357225789531838276676109830092400181402243325144092030253566067045309391758849310432709781082027026621306513787250611923558785098172755465204952231278685708006003328040156619"}, + {"2", "1.5", "2.8284271247461900976033774484193961571393437507538961463533594759814649569242140777007750686552831454700276924618245940498496721117014744252882429941998716628264453318550111855115999010023055641211429402191199432119405490691937240294570348372817783972191046584609686174286429016795252072559905028159793745067930926636176592812412305167047901094915006"}, + + {"1.5", "-1.5", "0.54433105395181735515495201660130919821465499570148225076282057050021341721273667256441320735658671884857657805035870869441308121329727940925017421138606190062864727722837257138836224561575817116077362459533037574525165407834346756306862420874990790396590549430251203206006004803871151962224035329063066957548905082088747351936846542240009860859723315"}, + {"2", "-1.5", "0.35355339059327376220042218105242451964241796884423701829416993449768311961552675971259688358191039318375346155772807425623120901396268430316103037427498395785330566648187639818894998762528819551514286752738999290149256863364921550368212935466022229965238808230762107717858036270994065090699881285199742181334913658295220741015515381458809876368643757"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + z := new(big.Float).SetPrec(prec) + z.Parse(test.z, 10) + w := new(big.Float).SetPrec(prec) + w.Parse(test.w, 10) + + x := bigfloat.Pow(z, w) + + if x.Cmp(want) != 0 { + t.Errorf("prec = %d, Pow(%v, %v) =\ngot %g;\nwant %g", prec, test.z, test.w, x, want) + } + } + } +} + +func TestPowIntegers(t *testing.T) { + for _, test := range []struct { + z, w string + want string + }{ + {"2", "5", "32"}, + {"2", "10", "1024"}, + {"2", "64", "18446744073709551616"}, + + {"2", "-5", "0.03125"}, + {"2", "-10", "0.0009765625"}, + {"2", "-64", "5.42101086242752217003726400434970855712890625e-20"}, + + {"1.5", "8", "25.62890625"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + z := new(big.Float).SetPrec(prec) + z.Parse(test.z, 10) + w := new(big.Float).SetPrec(prec) + w.Parse(test.w, 10) + + x := bigfloat.Pow(z, w) + + if x.Cmp(want) != 0 { + t.Errorf("prec = %d, Pow(%v, %v) =\ngot %g;\nwant %g", prec, test.z, test.w, x, want) + } + } + } +} + +func testPowFloat64(scale float64, nTests int, t *testing.T) { + for i := 0; i < nTests; i++ { + r1 := math.Abs(rand.Float64() * scale) // base always > 0 + r2 := rand.Float64() * scale + + z := big.NewFloat(r1).SetPrec(53) + w := big.NewFloat(r2).SetPrec(53) + + x64, acc := bigfloat.Pow(z, w).Float64() + + want := math.Pow(r1, r2) + + // Unfortunately, the Go math.Pow function is not completely + // accurate, so it doesn't make sense to require 100% + // compatibility with it, since it happens that math.Pow + // returns a result with the last bit off (same as math.Log). + // + // Just require a relative error smaller than 1e-14. + if math.Abs(x64-want)/want > 1e-14 || acc != big.Exact { + t.Errorf("Pow(%g, %g) =\n got %g (%s);\nwant %g (Exact)", z, w, x64, acc, want) + } + } +} + +func TestPowFloat64Small(t *testing.T) { + testPowFloat64(-100, 1e3, t) + testPowFloat64(-10, 1e3, t) + testPowFloat64(-1, 1e3, t) +} + +func TestPowFloat64Medium(t *testing.T) { + testPowFloat64(0.1, 4e3, t) + testPowFloat64(1, 4e3, t) +} + +func TestPowFloat64Big(t *testing.T) { + testPowFloat64(10, 4e3, t) + testPowFloat64(100, 4e3, t) +} + +func TestPowSpecialValues(t *testing.T) { + for _, f := range []struct { + z, w float64 + }{ + {2, +0.0}, + {2, -0.0}, + {4.2, 1.0}, + {math.Inf(+1), 2.0}, + } { + z := big.NewFloat(f.z).SetPrec(53) + w := big.NewFloat(f.w).SetPrec(53) + x64, acc := bigfloat.Pow(z, w).Float64() + want := math.Pow(f.z, f.w) + if x64 != want || acc != big.Exact { + t.Errorf("Pow(%g, %g) =\n got %g (%s);\nwant %g (Exact)", f.z, f.w, x64, acc, want) + } + } +} + +// ---------- Benchmarks ---------- + +func BenchmarkPowInt(b *testing.B) { + z := big.NewFloat(2).SetPrec(1e5) + w := big.NewFloat(50).SetPrec(1e5) + _ = bigfloat.Pow(z, w) // fill pi cache before benchmarking + + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + z = big.NewFloat(2).SetPrec(prec) + w = big.NewFloat(50).SetPrec(prec) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + bigfloat.Pow(z, w) + } + }) + } +} + +func BenchmarkPow(b *testing.B) { + z := big.NewFloat(2).SetPrec(1e5) + w := big.NewFloat(1.5).SetPrec(1e5) + _ = bigfloat.Pow(z, w) // fill pi cache before benchmarking + + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + z = big.NewFloat(2).SetPrec(prec) + w = big.NewFloat(1.5).SetPrec(prec) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + bigfloat.Pow(z, w) + } + }) + } +} diff --git a/math/sqrt_test.go b/math/sqrt_test.go new file mode 100644 index 000000000..699bfd7dc --- /dev/null +++ b/math/sqrt_test.go @@ -0,0 +1,130 @@ +package math_test + +import ( + "fmt" + "math" + "math/big" + "math/rand" + "testing" + + bigfloat "github.com/MinterTeam/minter-go-node/math" +) + +const maxPrec uint = 1100 + +// We can't guarantee that the result will have *prec* precision +// if we call Sqrt with an argument with *prec* precision, because +// the Newton's iteration will actually converge to a number that +// is not the square root of x with *prec+64* precision, but to a +// number that is the square root of x with *prec* precision. +// If we want Sqrt(x) with *prec* precision and correct rounding, +// we need to call Sqrt with an argument having precision greater +// than *prec*. +// +// This will happen for every test number which is not directly +// representable as a binary floating point value, so avoid those. + +func TestSqrt(t *testing.T) { + for _, test := range []struct { + z string + want string + }{ + // 350 decimal digits are enough to give us up to 1000 binary digits + {"0.5", "0.70710678118654752440084436210484903928483593768847403658833986899536623923105351942519376716382078636750692311545614851246241802792536860632206074854996791570661133296375279637789997525057639103028573505477998580298513726729843100736425870932044459930477616461524215435716072541988130181399762570399484362669827316590441482031030762917619752737287514"}, + {"2.0", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503"}, + {"3.0", "1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756756261414154067030299699450949989524788116555120943736485280932319023055820679748201010846749232650153123432669033228866506722546689218379712270471316603678615880190499865373798593894676503475065760507566183481296061009476021871903250831458295239598"}, + {"4.0", "2.0"}, + {"5", "2.2360679774997896964091736687312762354406183596115257242708972454105209256378048994144144083787822749695081761507737835042532677244470738635863601215334527088667781731918791658112766453226398565805357613504175337850034233924140644420864325390972525926272288762995174024406816117759089094984923713907297288984820886415426898940991316935770197486788844"}, + {"6", "2.4494897427831780981972840747058913919659474806566701284326925672509603774573150265398594331046402348185946012266141891248588654598377573416257839512372785528289127475276765712476301052709117702234813106789866908536324433525456040338088089393745855678465747243613041442702702161742018383000815898078380130897007286939936308371580944008004437386875492"}, + {"7", "2.6457513110645905905016157536392604257102591830824501803683344592010688232302836277603928864745436106150645783384974630957435298886272147844273905558801077227171507297283238922996895948650872607009780542037238280237159411003419391160015785255963059457410351523968027164073737990740415815199044034743194536713997305970050513996922375456160971190273782"}, + + {"1p512", "1p256"}, + {"1p1024", "1p512"}, + {"1p2048", "1p1024"}, + {"1p4096", "1p2048"}, + {"2p512", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p256"}, + {"2p1024", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p512"}, + {"2p2048", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p1024"}, + + {"1p-512", "1p-256"}, + {"1p-1024", "1p-512"}, + {"1p-2048", "1p-1024"}, + {"1p-4096", "1p-2048"}, + {"2p-512", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p-256"}, + {"2p-1024", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p-512"}, + {"2p-2048", "1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309122970249248360558507372126441214970999358314132226659275055927557999505011527820605714701095599716059702745345968620147285174186408891986095523292304843087143214508397626036279952514079896872533965463318088296406206152583523950547457503p-1024"}, + } { + for _, prec := range []uint{24, 53, 64, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000} { + want := new(big.Float).SetPrec(prec) + want.Parse(test.want, 10) + + z := new(big.Float).SetPrec(prec) + z.Parse(test.z, 10) + + x := bigfloat.Sqrt(z) + + if x.Cmp(want) != 0 { + t.Errorf("prec = %d, Sqrt(%v) =\ngot %g;\nwant %g", prec, test.z, x, want) + } + } + } +} + +func testSqrtFloat64(scale float64, nTests int, t *testing.T) { + for i := 0; i < nTests; i++ { + r := rand.Float64() * scale + + z := big.NewFloat(r) + x64, acc := bigfloat.Sqrt(z).Float64() + + want := math.Sqrt(r) + + if x64 != want || acc != big.Exact { + t.Errorf("Sqrt(%g) =\n got %g (%s);\nwant %g (Exact)", z, x64, acc, want) + } + } +} + +func TestSqrtFloat64Small(t *testing.T) { + testSqrtFloat64(1e-100, 1e5, t) + testSqrtFloat64(1e-10, 1e5, t) +} + +func TestSqrtFloa64Medium(t *testing.T) { + testSqrtFloat64(1, 1e5, t) + testSqrtFloat64(100, 1e5, t) +} + +func TestSqrtFloat64Big(t *testing.T) { + testSqrtFloat64(1e10, 1e5, t) + testSqrtFloat64(1e100, 1e5, t) +} + +func TestSqrtSpecialValues(t *testing.T) { + for _, f := range []float64{ + +0.0, + -0.0, + math.Inf(+1), + } { + z := big.NewFloat(f) + x64, acc := bigfloat.Sqrt(z).Float64() + want := math.Sqrt(f) + if x64 != want || acc != big.Exact { + t.Errorf("Sqrt(%g) =\n got %g (%s);\nwant %g (Exact)", z, x64, acc, want) + } + } +} + +// ---------- Benchmarks ---------- + +func BenchmarkSqrt(b *testing.B) { + for _, prec := range []uint{1e2, 1e3, 1e4, 1e5} { + z := big.NewFloat(2).SetPrec(prec) + b.Run(fmt.Sprintf("%v", prec), func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + bigfloat.Sqrt(z) + } + }) + } +} diff --git a/rlp/decode.go b/rlp/decode.go index 5676bed6d..8eaab8bdd 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -28,6 +28,7 @@ import ( "strings" ) +// Set of Errors var ( // EOL is returned when the end of the current list // has been reached during streaming. diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go index 7b1cfbf94..d95f22094 100644 --- a/rpc/lib/rpc_test.go +++ b/rpc/lib/rpc_test.go @@ -18,12 +18,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/log" - client "github.com/tendermint/tendermint/rpc/lib/client" - server "github.com/tendermint/tendermint/rpc/lib/server" - types "github.com/tendermint/tendermint/rpc/lib/types" + "github.com/tendermint/tendermint/rpc/jsonrpc/client" + "github.com/tendermint/tendermint/rpc/jsonrpc/server" + "github.com/tendermint/tendermint/rpc/jsonrpc/types" ) // Client and Server should work over tcp or unix sockets @@ -127,7 +127,7 @@ func setup() { if err != nil { panic(err) } - go server.StartHTTPServer(listener1, mux, tcpLogger, config) + go server.Serve(listener1, mux, tcpLogger, config) unixLogger := logger.With("socket", "unix") mux2 := http.NewServeMux() @@ -139,7 +139,7 @@ func setup() { if err != nil { panic(err) } - go server.StartHTTPServer(listener2, mux2, unixLogger, config) + go server.Serve(listener2, mux2, unixLogger, config) // wait for servers to start time.Sleep(time.Second * 2) @@ -272,15 +272,15 @@ func testWithWSClient(t *testing.T, cl *client.WSClient) { func TestServersAndClientsBasic(t *testing.T) { serverAddrs := [...]string{tcpAddr, unixAddr} for _, addr := range serverAddrs { - cl1, _ := client.NewURIClient(addr) + cl1, _ := client.NewURI(addr) fmt.Printf("=== testing server on %s using URI client", addr) testWithHTTPClient(t, cl1) - cl2, _ := client.NewJSONRPCClient(addr) + cl2, _ := client.New(addr) fmt.Printf("=== testing server on %s using JSONRPC client", addr) testWithHTTPClient(t, cl2) - cl3, _ := client.NewWSClient(addr, websocketEndpoint) + cl3, _ := client.NewWS(addr, websocketEndpoint) cl3.SetLogger(log.TestingLogger()) err := cl3.Start() require.Nil(t, err) @@ -291,7 +291,7 @@ func TestServersAndClientsBasic(t *testing.T) { } func TestHexStringArg(t *testing.T) { - cl, _ := client.NewURIClient(tcpAddr) + cl, _ := client.NewURI(tcpAddr) // should NOT be handled as hex val := "0xabc" got, err := echoViaHTTP(cl, val) @@ -300,7 +300,7 @@ func TestHexStringArg(t *testing.T) { } func TestQuotedStringArg(t *testing.T) { - cl, _ := client.NewURIClient(tcpAddr) + cl, _ := client.NewURI(tcpAddr) // should NOT be unquoted val := "\"abc\"" got, err := echoViaHTTP(cl, val) @@ -309,7 +309,7 @@ func TestQuotedStringArg(t *testing.T) { } func TestWSNewWSRPCFunc(t *testing.T) { - cl, _ := client.NewWSClient(tcpAddr, websocketEndpoint) + cl, _ := client.NewWS(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() require.Nil(t, err) @@ -334,7 +334,7 @@ func TestWSNewWSRPCFunc(t *testing.T) { } func TestWSHandlesArrayParams(t *testing.T) { - cl, _ := client.NewWSClient(tcpAddr, websocketEndpoint) + cl, _ := client.NewWS(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() require.Nil(t, err) @@ -359,7 +359,7 @@ func TestWSHandlesArrayParams(t *testing.T) { // TestWSClientPingPong checks that a client & server exchange pings // & pongs so connection stays alive. func TestWSClientPingPong(t *testing.T) { - cl, _ := client.NewWSClient(tcpAddr, websocketEndpoint) + cl, _ := client.NewWS(tcpAddr, websocketEndpoint) cl.SetLogger(log.TestingLogger()) err := cl.Start() require.Nil(t, err) diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go index 90a262430..2b9c1c1e4 100644 --- a/rpc/lib/server/http_server.go +++ b/rpc/lib/server/http_server.go @@ -124,6 +124,10 @@ func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler begin := time.Now() rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix())) + rww.Header().Set("Deprecation", "version=\"v1\"") + // rww.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"successor-version\"","")) + // rww.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"deprecation\"","")) + // rww.Header().Set("Sunset", time.Unix(0,0).Format(time.RFC1123)) defer func() { // Send a 500 error if a panic happens during a handler. diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index bc6492238..40896e0a2 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -20,14 +20,14 @@ import ( "github.com/tendermint/tendermint/proxy" ctypes "github.com/tendermint/tendermint/rpc/core/types" core_grpc "github.com/tendermint/tendermint/rpc/grpc" - rpcclient "github.com/tendermint/tendermint/rpc/lib/client" + rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" ) var globalConfig *cfg.Config func waitForRPC() { laddr := GetConfig().RPC.ListenAddress - client, _ := rpcclient.NewJSONRPCClient(laddr) + client, _ := rpcclient.New(laddr) ctypes.RegisterAmino(client.Codec()) result := new(ctypes.ResultStatus) for { diff --git a/scripts/DOCKER/Dockerfile b/scripts/DOCKER/Dockerfile index ec4779db1..31f7e34de 100644 --- a/scripts/DOCKER/Dockerfile +++ b/scripts/DOCKER/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:18.04 RUN apt-get update && apt-get install -y software-properties-common build-essential wget -RUN wget https://dl.google.com/go/go1.14.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.14.linux-amd64.tar.gz +RUN wget https://dl.google.com/go/go1.15.2.linux-amd64.tar.gz && tar -C /usr/local -xzf go1.15.2.linux-amd64.tar.gz ENV GOPATH=$HOME/go ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin diff --git a/scripts/dist.sh b/scripts/dist.sh index bd934464a..d7595d04a 100644 --- a/scripts/dist.sh +++ b/scripts/dist.sh @@ -23,12 +23,6 @@ mkdir -p build/pkg GIT_COMMIT="$(git rev-parse --short=8 HEAD)" GIT_IMPORT="github.com/MinterTeam/minter-go-node/version" -# Make sure build tools are available. -make get_tools - -# Get VENDORED dependencies -make get_vendor_deps - #packr # Build! @@ -38,7 +32,7 @@ echo "==> Building for mac os..." CGO_ENABLED=1 go build -tags "minter gcc" -ldflags "-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}" -o "build/pkg/darwin_amd64/minter" ./cmd/minter echo "==> Building for linux in docker" -docker run -t -v ${PWD}:/go/src/github.com/MinterTeam/minter-go-node/ -i minter-builder-1:latest sh -c 'CGO_ENABLED=1 go build -tags "minter gcc" -ldflags "-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}" -o "build/pkg/linux_amd64/minter" ./cmd/minter/' +docker run -t -v ${PWD}:/go/src/github.com/MinterTeam/minter-go-node/ -i minter-builder-1:latest sh -c "CGO_ENABLED=1 go build -tags 'minter gcc' -ldflags '-s -w -X ${GIT_IMPORT}.GitCommit=${GIT_COMMIT}' -o 'build/pkg/linux_amd64/minter' ./cmd/minter/" # Zip all the files. echo "==> Packaging..." @@ -51,17 +45,17 @@ for PLATFORM in $(find ./build/pkg -mindepth 1 -maxdepth 1 -type d); do popd >/dev/null 2>&1 done -# Add "minter" and $VERSION prefix to package name. +# Add "minter" and $VERSION, $GIT_COMMIT prefix to package name. rm -rf ./build/dist mkdir -p ./build/dist for FILENAME in $(find ./build/pkg -mindepth 1 -maxdepth 1 -type f); do FILENAME=$(basename "$FILENAME") - cp "./build/pkg/${FILENAME}" "./build/dist/minter_${VERSION}_${FILENAME}" + cp "./build/pkg/${FILENAME}" "./build/dist/minter_${VERSION}_${GIT_COMMIT}_${FILENAME}" done # Make the checksums. pushd ./build/dist -shasum -a256 ./* > "./minter_${VERSION}_SHA256SUMS" +shasum -a256 ./* > "./minter_${VERSION}_${GIT_COMMIT}_SHA256SUMS" popd # Done diff --git a/tests/byz_test.go b/tests/byz_test.go new file mode 100644 index 000000000..58b3b1462 --- /dev/null +++ b/tests/byz_test.go @@ -0,0 +1,101 @@ +package tests + +import ( + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + tmTypes "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/ed25519" + "math/big" + "testing" + "time" +) + +func TestBlockchain_ByzantineValidators(t *testing.T) { + state := DefaultAppState() // generate default state + + stake := helpers.BipToPip(big.NewInt(1000)).String() + state.Validators = append(state.Validators, types.Validator{ + TotalBipStake: stake, + PubKey: types.Pubkey{1}, + AccumReward: "10", + AbsentTimes: nil, + }) + state.Candidates = append(state.Candidates, types.Candidate{ + ID: 1, + RewardAddress: types.Address{}, + OwnerAddress: types.Address{}, + ControlAddress: types.Address{}, + TotalBipStake: stake, + PubKey: types.Pubkey{1}, + Commission: 10, + Stakes: []types.Stake{ + { + Owner: types.Address{}, + Coin: 0, + Value: stake, + BipValue: stake, + }, + }, + Updates: nil, + Status: 2, + }) + + var pubkey ed25519.PubKeyEd25519 + copy(pubkey[:], types.Pubkey{1}.Bytes()) + var address types.TmAddress + copy(address[:], pubkey.Address().Bytes()) + + app := CreateApp(state) // create application + req := tmTypes.RequestBeginBlock{ + Hash: nil, + Header: tmTypes.Header{ + Version: tmTypes.Version{}, + ChainID: "", + Height: 1, + Time: time.Time{}, + LastBlockId: tmTypes.BlockID{}, + LastCommitHash: nil, + DataHash: nil, + ValidatorsHash: nil, + NextValidatorsHash: nil, + ConsensusHash: nil, + AppHash: nil, + LastResultsHash: nil, + EvidenceHash: nil, + ProposerAddress: nil, + }, + LastCommitInfo: tmTypes.LastCommitInfo{ + Round: 0, + Votes: nil, + }, + ByzantineValidators: []tmTypes.Evidence{ + { + Type: "", + Validator: tmTypes.Validator{ + Address: address[:], + Power: 10, + }, + Height: 1, + Time: time.Time{}, + TotalVotingPower: 0, + }, + }, + } + app.BeginBlock(req) + // SendBeginBlock(app) // send BeginBlock + + SendEndBlock(app) // send EndBlock + SendCommit(app) // send Commit + + if validator := app.CurrentState().Validators().GetByPublicKey([32]byte{1}); validator != nil { + t.Error("validator exists") + } + candidate := app.CurrentState().Candidates().GetCandidate([32]byte{1}) + if candidate == nil { + t.Fatal("candidate is not exists") + } + if candidate.GetTotalBipStake().String() != "0" { + t.Fatal("candidate total bip stake is not 0") + } + +} diff --git a/tests/helpers.go b/tests/helpers.go new file mode 100644 index 000000000..59007e0e8 --- /dev/null +++ b/tests/helpers.go @@ -0,0 +1,151 @@ +package tests + +import ( + "crypto/ecdsa" + "github.com/MinterTeam/minter-go-node/cmd/utils" + "github.com/MinterTeam/minter-go-node/config" + "github.com/MinterTeam/minter-go-node/core/minter" + "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/crypto" + "github.com/MinterTeam/minter-go-node/rlp" + "github.com/tendermint/go-amino" + tmTypes "github.com/tendermint/tendermint/abci/types" + "os" + "path/filepath" + "time" +) + +// CreateApp creates and returns new Blockchain instance +// Recreates $HOME/.minter_test dir +func CreateApp(state types.AppState) *minter.Blockchain { + utils.MinterHome = os.ExpandEnv(filepath.Join("$HOME", ".minter_test")) + _ = os.RemoveAll(utils.MinterHome) + + jsonState, err := amino.MarshalJSON(state) + if err != nil { + panic(err) + } + + cfg := config.GetConfig() + app := minter.NewMinterBlockchain(cfg) + app.InitChain(tmTypes.RequestInitChain{ + Time: time.Now(), + ChainId: "test", + Validators: []tmTypes.ValidatorUpdate{ + { + PubKey: tmTypes.PubKey{}, + Power: 1, + }, + }, + AppStateBytes: jsonState, + }) + + return app +} + +// SendCommit sends Commit message to given Blockchain instance +func SendCommit(app *minter.Blockchain) tmTypes.ResponseCommit { + return app.Commit() +} + +// SendBeginBlock sends BeginBlock message to given Blockchain instance +func SendBeginBlock(app *minter.Blockchain) tmTypes.ResponseBeginBlock { + return app.BeginBlock(tmTypes.RequestBeginBlock{ + Hash: nil, + Header: tmTypes.Header{ + Version: tmTypes.Version{}, + ChainID: "", + Height: 1, + Time: time.Time{}, + LastBlockId: tmTypes.BlockID{}, + LastCommitHash: nil, + DataHash: nil, + ValidatorsHash: nil, + NextValidatorsHash: nil, + ConsensusHash: nil, + AppHash: nil, + LastResultsHash: nil, + EvidenceHash: nil, + ProposerAddress: nil, + }, + LastCommitInfo: tmTypes.LastCommitInfo{ + Round: 0, + Votes: nil, + }, + ByzantineValidators: nil, + }) +} + +// SendEndBlock sends EndBlock message to given Blockchain instance +func SendEndBlock(app *minter.Blockchain) tmTypes.ResponseEndBlock { + return app.EndBlock(tmTypes.RequestEndBlock{ + Height: 0, + }) +} + +// CreateTx composes and returns Tx with given params. +// Nonce, chain id, gas price, gas coin and signature type fields are auto-filled. +func CreateTx(app *minter.Blockchain, address types.Address, txType transaction.TxType, data interface{}) transaction.Transaction { + nonce := app.CurrentState().Accounts().GetNonce(address) + 1 + bData, err := rlp.EncodeToBytes(data) + if err != nil { + panic(err) + } + + tx := transaction.Transaction{ + Nonce: nonce, + ChainID: types.CurrentChainID, + GasPrice: 1, + GasCoin: types.GetBaseCoinID(), + Type: txType, + Data: bData, + SignatureType: transaction.SigTypeSingle, + } + + return tx +} + +// SendTx sends DeliverTx message to given Blockchain instance +func SendTx(app *minter.Blockchain, bytes []byte) tmTypes.ResponseDeliverTx { + return app.DeliverTx(tmTypes.RequestDeliverTx{ + Tx: bytes, + }) +} + +// SignTx returns bytes of signed with given pk transaction +func SignTx(pk *ecdsa.PrivateKey, tx transaction.Transaction) []byte { + err := tx.Sign(pk) + if err != nil { + panic(err) + } + + b, _ := rlp.EncodeToBytes(tx) + + return b +} + +// CreateAddress returns random address and corresponding private key +func CreateAddress() (types.Address, *ecdsa.PrivateKey) { + pk, _ := crypto.GenerateKey() + + return crypto.PubkeyToAddress(pk.PublicKey), pk +} + +// DefaultAppState returns new AppState with some predefined values +func DefaultAppState() types.AppState { + return types.AppState{ + Note: "", + StartHeight: 1, + Validators: nil, + Candidates: nil, + BlockListCandidates: nil, + Accounts: nil, + Coins: nil, + FrozenFunds: nil, + HaltBlocks: nil, + UsedChecks: nil, + MaxGas: 0, + TotalSlashed: "0", + } +} diff --git a/tests/send_test.go b/tests/send_test.go new file mode 100644 index 000000000..3726475a4 --- /dev/null +++ b/tests/send_test.go @@ -0,0 +1,67 @@ +package tests + +import ( + "github.com/MinterTeam/minter-go-node/core/code" + "github.com/MinterTeam/minter-go-node/core/transaction" + "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/helpers" + "math/big" + "testing" +) + +func TestSend(t *testing.T) { + address, pk := CreateAddress() // create account for test + + state := DefaultAppState() // generate default state + + // add address to genesis state + state.Accounts = append(state.Accounts, types.Account{ + Address: address, + Balance: []types.Balance{ + { + Coin: uint64(types.GetBaseCoinID()), + Value: helpers.BipToPip(big.NewInt(1)).String(), + }, + }, + Nonce: 0, + MultisigData: nil, + }) + + app := CreateApp(state) // create application + SendBeginBlock(app) // send BeginBlock + + recipient, _ := CreateAddress() // generate recipient + value := big.NewInt(1) + + tx := CreateTx(app, address, transaction.TypeSend, transaction.SendData{ + Coin: types.GetBaseCoinID(), + To: recipient, + Value: value, + }) + + response := SendTx(app, SignTx(pk, tx)) // compose and send tx + + // check that result is OK + if response.Code != code.OK { + t.Fatalf("Response code is not OK: %s, %d", response.Log, response.Code) + } + + SendEndBlock(app) // send EndBlock + SendCommit(app) // send Commit + + // check recipient's balance + { + balance := app.CurrentState().Accounts().GetBalance(recipient, types.GetBaseCoinID()) + if balance.Cmp(value) != 0 { + t.Fatalf("Recipient balance is not correct. Expected %s, got %s", value, balance) + } + } + + // check sender's balance + { + balance := app.CurrentState().Accounts().GetBalance(address, types.GetBaseCoinID()) + if balance.String() != "989999999999999999" { + t.Fatalf("Recipient balance is not correct. Expected %s, got %s", "989999999999999999", balance) + } + } +} diff --git a/tree/tree.go b/tree/tree.go index 910c48147..82954ad33 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -6,38 +6,65 @@ import ( "sync" ) -type Tree interface { +// ReadOnlyTree used for CheckState: API and CheckTx calls. Immutable. +type ReadOnlyTree interface { Get(key []byte) (index int64, value []byte) + Version() int64 + Hash() []byte + Iterate(fn func(key []byte, value []byte) bool) (stopped bool) +} + +// MTree mutable tree, used for txs delivery +type MTree interface { + ReadOnlyTree + KeepLastHeight() int64 + AvailableVersions() []int Set(key, value []byte) bool Remove(key []byte) ([]byte, bool) LoadVersion(targetVersion int64) (int64, error) LazyLoadVersion(targetVersion int64) (int64, error) SaveVersion() ([]byte, int64, error) - DeleteVersion(version int64) error + DeleteVersionsIfExists(from, to int64) error + DeleteVersionIfExists(version int64) error GetImmutable() *ImmutableTree GetImmutableAtHeight(version int64) (*ImmutableTree, error) - Version() int64 - Hash() []byte - Iterate(fn func(key []byte, value []byte) bool) (stopped bool) + GlobalLock() + GlobalUnlock() } -func NewMutableTree(db dbm.DB, cacheSize int) *MutableTree { +// NewMutableTree creates and returns new MutableTree using given db. Panics on error. +// If you want to get read-only state, you should use height = 0 and LazyLoadVersion (version), see NewImmutableTree +func NewMutableTree(height uint64, db dbm.DB, cacheSize int) (MTree, error) { tree, err := iavl.NewMutableTree(db, cacheSize) if err != nil { - panic(err) + return nil, err } - return &MutableTree{ - tree: tree, + + if height == 0 { + return &mutableTree{ + tree: tree, + }, nil + } + + if _, err := tree.LoadVersionForOverwriting(int64(height)); err != nil { + return nil, err } + + return &mutableTree{ + tree: tree, + }, nil } -type MutableTree struct { +type mutableTree struct { tree *iavl.MutableTree - lock sync.RWMutex + sync.Mutex } -func (t *MutableTree) GetImmutableAtHeight(version int64) (*ImmutableTree, error) { +func (t *mutableTree) GetImmutableAtHeight(version int64) (*ImmutableTree, error) { + t.lock.RLock() + defer t.lock.RUnlock() + tree, err := t.tree.GetImmutable(version) if err != nil { return nil, err @@ -48,25 +75,36 @@ func (t *MutableTree) GetImmutableAtHeight(version int64) (*ImmutableTree, error }, nil } -func (t *MutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped bool) { +func (t *mutableTree) GlobalLock() { + t.Lock() +} + +func (t *mutableTree) GlobalUnlock() { + t.Unlock() +} + +func (t *mutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped bool) { + t.lock.RLock() + defer t.lock.RUnlock() + return t.tree.Iterate(fn) } -func (t *MutableTree) Hash() []byte { +func (t *mutableTree) Hash() []byte { t.lock.RLock() defer t.lock.RUnlock() return t.tree.Hash() } -func (t *MutableTree) Version() int64 { +func (t *mutableTree) Version() int64 { t.lock.RLock() defer t.lock.RUnlock() return t.tree.Version() } -func (t *MutableTree) GetImmutable() *ImmutableTree { +func (t *mutableTree) GetImmutable() *ImmutableTree { t.lock.RLock() defer t.lock.RUnlock() @@ -75,109 +113,132 @@ func (t *MutableTree) GetImmutable() *ImmutableTree { } } -func (t *MutableTree) Get(key []byte) (index int64, value []byte) { +func (t *mutableTree) Get(key []byte) (index int64, value []byte) { t.lock.RLock() defer t.lock.RUnlock() return t.tree.Get(key) } -func (t *MutableTree) Set(key, value []byte) bool { +func (t *mutableTree) Set(key, value []byte) bool { t.lock.Lock() defer t.lock.Unlock() return t.tree.Set(key, value) } -func (t *MutableTree) Remove(key []byte) ([]byte, bool) { +func (t *mutableTree) Remove(key []byte) ([]byte, bool) { t.lock.Lock() defer t.lock.Unlock() return t.tree.Remove(key) } -func (t *MutableTree) LoadVersion(targetVersion int64) (int64, error) { +func (t *mutableTree) LoadVersion(targetVersion int64) (int64, error) { t.lock.Lock() defer t.lock.Unlock() return t.tree.LoadVersion(targetVersion) } -func (t *MutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { +func (t *mutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { t.lock.Lock() defer t.lock.Unlock() return t.tree.LazyLoadVersion(targetVersion) } -func (t *MutableTree) SaveVersion() ([]byte, int64, error) { +// Should use GlobalLock() and GlobalUnlock +func (t *mutableTree) SaveVersion() ([]byte, int64, error) { t.lock.Lock() defer t.lock.Unlock() return t.tree.SaveVersion() } -func (t *MutableTree) DeleteVersion(version int64) error { +// Should use GlobalLock() and GlobalUnlock +func (t *mutableTree) DeleteVersionsIfExists(from, to int64) error { + t.lock.Lock() + defer t.lock.Unlock() + + var existsVersions = make([]int64, 0, to-from) + for i := from; i < to; i++ { + if t.tree.VersionExists(i) { + existsVersions = append(existsVersions, i) + } + } + return t.tree.DeleteVersions(existsVersions...) +} + +// Should use GlobalLock() and GlobalUnlock +func (t *mutableTree) DeleteVersionIfExists(version int64) error { t.lock.Lock() defer t.lock.Unlock() + if !t.tree.VersionExists(version) { + return nil + } return t.tree.DeleteVersion(version) } -func NewImmutableTree(db dbm.DB) *ImmutableTree { - return &ImmutableTree{ - tree: iavl.NewImmutableTree(db, 1024), +func (t *mutableTree) KeepLastHeight() int64 { + t.lock.RLock() + defer t.lock.RUnlock() + + versions := t.tree.AvailableVersions() + prev := 1 + for _, version := range versions { + if version-prev == 1 { + break + } + prev = version } + + return int64(prev) } +func (t *mutableTree) AvailableVersions() []int { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.tree.AvailableVersions() +} + +// ImmutableTree used for CheckState: API and CheckTx calls. type ImmutableTree struct { tree *iavl.ImmutableTree } +// NewImmutableTree returns MTree from given db at given height +// Warning: returns the MTree interface, but you should only use ReadOnlyTree +func NewImmutableTree(height uint64, db dbm.DB) (MTree, error) { + tree, _ := NewMutableTree(0, db, 1024) + _, err := tree.LazyLoadVersion(int64(height)) + if err != nil { + return nil, err + } + return tree, nil +} + +// Iterate iterates over all keys of the tree, in order. The keys and values must not be modified, +// since they may point to data stored within IAVL. func (t *ImmutableTree) Iterate(fn func(key []byte, value []byte) bool) (stopped bool) { return t.tree.Iterate(fn) } +// Hash returns the root hash. func (t *ImmutableTree) Hash() []byte { return t.tree.Hash() } +// Version returns the version of the tree. func (t *ImmutableTree) Version() int64 { return t.tree.Version() } -func (t *ImmutableTree) GetImmutable() *ImmutableTree { - return t -} - +// Get returns the index and value of the specified key if it exists, or nil and the next index +// otherwise. The returned value must not be modified, since it may point to data stored within +// IAVL. func (t *ImmutableTree) Get(key []byte) (index int64, value []byte) { return t.tree.Get(key) } - -func (t *ImmutableTree) GetImmutableAtHeight(version int64) (*ImmutableTree, error) { - panic("Not implemented") -} - -func (t *ImmutableTree) Set(key, value []byte) bool { - panic("Not implemented") -} - -func (t *ImmutableTree) Remove(key []byte) ([]byte, bool) { - panic("Not implemented") -} - -func (t *ImmutableTree) LoadVersion(targetVersion int64) (int64, error) { - panic("Not implemented") -} - -func (t *ImmutableTree) LazyLoadVersion(targetVersion int64) (int64, error) { - panic("Not implemented") -} - -func (t *ImmutableTree) SaveVersion() ([]byte, int64, error) { - panic("Not implemented") -} - -func (t *ImmutableTree) DeleteVersion(version int64) error { - panic("Not implemented") -} diff --git a/upgrades/blocks.go b/upgrades/blocks.go index dff9e64c0..6aad29a9b 100644 --- a/upgrades/blocks.go +++ b/upgrades/blocks.go @@ -1,18 +1,13 @@ package upgrades -const UpgradeBlock1 = 5000 -const UpgradeBlock2 = 38519 -const UpgradeBlock3 = 109000 -const UpgradeBlock4 = 3183842 - -func IsUpgradeBlock(height uint64) bool { - upgradeBlocks := []uint64{UpgradeBlock1, UpgradeBlock2, UpgradeBlock3, UpgradeBlock4} - - for _, block := range upgradeBlocks { - if height == block { - return true - } - } - - return false -} +//func IsUpgradeBlock(height uint64) bool { +// upgradeBlocks := []uint64{} // fill this +// +// for _, block := range upgradeBlocks { +// if height == block { +// return true +// } +// } +// +// return false +//} diff --git a/upgrades/grace.go b/upgrades/grace.go index e350ab55f..d41a6eef2 100644 --- a/upgrades/grace.go +++ b/upgrades/grace.go @@ -1,11 +1,17 @@ package upgrades var gracePeriods = []*gracePeriod{ - NewGracePeriod(1, 120), - NewGracePeriod(UpgradeBlock1, UpgradeBlock1+120), - NewGracePeriod(UpgradeBlock2, UpgradeBlock2+120), - NewGracePeriod(UpgradeBlock3, UpgradeBlock3+120), - NewGracePeriod(UpgradeBlock4, UpgradeBlock4+120), + newGracePeriod(1, 120), +} + +func IsGraceBlock(block uint64) bool { + for _, gp := range gracePeriods { + if gp.isApplicable(block) { + return true + } + } + + return false } type gracePeriod struct { @@ -13,20 +19,10 @@ type gracePeriod struct { to uint64 } -func (gp *gracePeriod) IsApplicable(block uint64) bool { +func (gp *gracePeriod) isApplicable(block uint64) bool { return block >= gp.from && block <= gp.to } -func NewGracePeriod(from uint64, to uint64) *gracePeriod { +func newGracePeriod(from uint64, to uint64) *gracePeriod { return &gracePeriod{from: from, to: to} } - -func IsGraceBlock(block uint64) bool { - for _, gp := range gracePeriods { - if gp.IsApplicable(block) { - return true - } - } - - return false -} diff --git a/version/version.go b/version/version.go index e9e39b4e2..4b4e95d98 100755 --- a/version/version.go +++ b/version/version.go @@ -2,16 +2,12 @@ package version // Version components const ( - Maj = "1" - Min = "1" - Fix = "9" - - AppVer = 6 + AppVer = 7 ) var ( - // Must be a string because scripts like dist.sh read this file. - Version = "1.1.9" + // Version must be a string because scripts like dist.sh read this file. + Version = "1.2.0" // GitCommit is the current HEAD set using ldflags. GitCommit string