├── .gitignore ├── rpc ├── .test-fixtures │ ├── counter.json │ ├── balance.json │ ├── chain_id.json │ ├── ballots.json │ ├── active_chains.json │ ├── commit.json │ ├── rpc_errors.json │ ├── blocks.json │ ├── frozen_balance.json │ ├── version.json │ ├── bootstrap.json │ ├── cycle.json │ ├── invalid_block.json │ ├── invalid_blocks.json │ ├── proposals.json │ ├── operation_metadata_hashes.json │ ├── protocols.json │ ├── current_level.json │ ├── protocol_data.json │ ├── parse_operations.json │ ├── header_shell.json │ ├── checkpoint.json │ ├── header.json │ ├── frozen_balance_by_cycle.json │ ├── constants.json │ ├── delegated_contracts.json │ ├── operation_hashes.json │ ├── preapply_operations.json │ ├── metadata.json │ ├── endorsing_rights.json │ ├── connections.json │ ├── live_blocks.json │ ├── baking_rights.json │ ├── delegate.json │ ├── ballot_list.json │ └── entrypoints.json ├── iface_test.go ├── client_test.go ├── helpers_integration_test.go ├── votes_integration_test.go ├── client.go ├── block_integration_test.go ├── iface.go ├── independent_test.go ├── independent.go ├── votes.go ├── votes_test.go ├── context_integration_test.go └── mocks_test.go ├── internal ├── testutils │ └── utils.go └── crypto │ └── crypto.go ├── go.mod ├── CHANGELOG.md ├── LICENSE.md ├── keys ├── signature.go ├── curve.go ├── ed25519.go ├── pubKey.go ├── integration_test.go ├── nistP256.go ├── secp256k1.go ├── key.go └── key_test.go ├── Makefile ├── example └── transaction │ └── transaction.go ├── README.md └── forge └── forge_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /rpc/.test-fixtures/counter.json: -------------------------------------------------------------------------------- 1 | "10" -------------------------------------------------------------------------------- /rpc/.test-fixtures/balance.json: -------------------------------------------------------------------------------- 1 | "1216660108948" -------------------------------------------------------------------------------- /rpc/.test-fixtures/chain_id.json: -------------------------------------------------------------------------------- 1 | "NetXdQprcVkpaWU" -------------------------------------------------------------------------------- /rpc/.test-fixtures/ballots.json: -------------------------------------------------------------------------------- 1 | {"yay":28379,"nay":2684,"pass":22146} -------------------------------------------------------------------------------- /rpc/.test-fixtures/active_chains.json: -------------------------------------------------------------------------------- 1 | [{"chain_id":"NetXdQprcVkpaWU"}] -------------------------------------------------------------------------------- /rpc/.test-fixtures/commit.json: -------------------------------------------------------------------------------- 1 | "47e6a0f0134335480f728e245b77461190ca5ac4" -------------------------------------------------------------------------------- /rpc/.test-fixtures/rpc_errors.json: -------------------------------------------------------------------------------- 1 | [{"kind":"somekind","error":"someerror"}] -------------------------------------------------------------------------------- /rpc/.test-fixtures/blocks.json: -------------------------------------------------------------------------------- 1 | [["BLUdLeoqJtswBAmboRjokR8bM8aiD22FzfM2LVVp5NR8sxLt15r"]] -------------------------------------------------------------------------------- /rpc/.test-fixtures/frozen_balance.json: -------------------------------------------------------------------------------- 1 | {"deposits":"15296000000","fees":"76724","rewards":"474800000"} -------------------------------------------------------------------------------- /rpc/.test-fixtures/version.json: -------------------------------------------------------------------------------- 1 | {"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0} -------------------------------------------------------------------------------- /rpc/.test-fixtures/bootstrap.json: -------------------------------------------------------------------------------- 1 | {"block":"BKoarwjfdpFP9W3pAeYLxXDJ3pgc3yTeCV75PceUpbrR1BreDqt","timestamp":"2019-12-12T11:26:11Z"} -------------------------------------------------------------------------------- /rpc/.test-fixtures/cycle.json: -------------------------------------------------------------------------------- 1 | {"last_roll":[],"nonces":[],"random_seed":"04dca5c197fc2e18309b60844148c55fc7ccdbcb498bd57acd4ac29f16e22846","roll_snapshot":4} -------------------------------------------------------------------------------- /rpc/.test-fixtures/invalid_block.json: -------------------------------------------------------------------------------- 1 | {"block":"BLzGD63HA4RP8Fh5xEtvdQSMKa2WzJMZjQPNVUc4Rqy8Lh5BEY1","level":10,"errors":[{"kind":"err","error":"message"}]} -------------------------------------------------------------------------------- /rpc/.test-fixtures/invalid_blocks.json: -------------------------------------------------------------------------------- 1 | [{"block":"BLzGD63HA4RP8Fh5xEtvdQSMKa2WzJMZjQPNVUc4Rqy8Lh5BEY1","level":10,"errors":[{"kind":"err","error":"message"}]}] -------------------------------------------------------------------------------- /rpc/.test-fixtures/proposals.json: -------------------------------------------------------------------------------- 1 | [["PsBABY5HQTSkA4297zNHfsZNKtxULfL18y95qb3m53QJiXGmrbU",12680],["PsBABY5nk4JhdEv1N1pZbt6m6ccB9BfNqa23iKZcHBh23jmRS9f",9518]] -------------------------------------------------------------------------------- /rpc/.test-fixtures/operation_metadata_hashes.json: -------------------------------------------------------------------------------- 1 | [["ontoZH3KLbMdNJU6HfGakKos1pcBLEQS55Ux3ZCapHWGpL4v17N","ooWFa5HJd9tUiHfPyVoqdksspRzBgW1kC8AjoAFWZhrn2v54wNY"]] -------------------------------------------------------------------------------- /rpc/.test-fixtures/protocols.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", 3 | "next_protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo" 4 | } -------------------------------------------------------------------------------- /rpc/.test-fixtures/current_level.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": 1275618, 3 | "level_position": 1275617, 4 | "cycle": 311, 5 | "cycle_position": 1761, 6 | "voting_period": 38, 7 | "voting_period_position": 30433, 8 | "expected_commitment": false 9 | } -------------------------------------------------------------------------------- /rpc/.test-fixtures/protocol_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", 3 | "priority": 0, 4 | "proof_of_work_nonce": "e69b63f1c37c0000", 5 | "signature": "sigNqRFU1kDREEcxsD5gfxHayi6RQvjj7nii6VdxpzKWJbxCBiDuudoVRjR94yJ3Ujfc4RGGxTDo7SWoto9oADtqYDvW2ZxE" 6 | } -------------------------------------------------------------------------------- /internal/testutils/utils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | // CheckErr - 10 | func CheckErr(t *testing.T, wantErr bool, errContains string, err error) { 11 | if wantErr { 12 | assert.Error(t, err) 13 | if err != nil { 14 | assert.Contains(t, err.Error(), errContains) 15 | } 16 | } else { 17 | assert.Nil(t, err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/parse_operations.json: -------------------------------------------------------------------------------- 1 | [{"branch":"BLz6yCE4BUL4ppo1zsEWdK9FRCt15WAY7ECQcuK9RtWg4xeEVL7","contents":[{"kind":"transaction","source":"tz1LSAycAVcNdYnXCy18bwVksXci8gUC2YpA","fee":"10100","counter":"10","gas_limit":"10100","storage_limit":"0","amount":"12345","destination":"tz1LSAycAVcNdYnXCy18bwVksXci8gUC2YpA"}],"signature":"edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q"}] -------------------------------------------------------------------------------- /rpc/iface_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "net/http/httptest" 5 | "testing" 6 | 7 | "github.com/goat-systems/go-tezos/v4/rpc" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func Test_iface(t *testing.T) { 12 | server := httptest.NewServer(gtGoldenHTTPMock(blankHandler)) 13 | defer server.Close() 14 | 15 | var r rpc.IFace 16 | var err error 17 | r, err = rpc.New(server.URL) 18 | assert.Nil(t, err) 19 | assert.NotNil(t, r) 20 | } 21 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/header_shell.json: -------------------------------------------------------------------------------- 1 | { 2 | "level": 1275510, 3 | "proto": 7, 4 | "predecessor": "BLNTRAcHTfnecx6np8wxDZ9VjQiUmWSiDXj2U5sUbYk9ed9cbiu", 5 | "timestamp": "2020-12-26T20:58:04Z", 6 | "validation_pass": 4, 7 | "operations_hash": "LLob1rczMeVVwVAR1mLvJNCaN58DZQKt2R17gPBwXCXcd9rkPb7i2", 8 | "fitness": [ 9 | "01", 10 | "0000000000097676" 11 | ], 12 | "context": "CoUgZWxgymKV8Yj2h4yhW1vXUne2VQje3YEQbABbuu7XV9zLbYq7" 13 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/goat-systems/go-tezos/v4 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/btcsuite/btcutil v1.0.2 7 | github.com/ethereum/go-ethereum v1.9.23 8 | github.com/go-playground/validator/v10 v10.2.0 9 | github.com/go-resty/resty/v2 v2.3.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/stretchr/testify v1.5.1 12 | github.com/tyler-smith/go-bip39 v1.0.2 13 | github.com/valyala/fastjson v1.5.4 14 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a 15 | ) 16 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/checkpoint.json: -------------------------------------------------------------------------------- 1 | {"block":{"level":38913,"proto":2,"predecessor":"BMKAi2DP2PrLR6vkmdbaR4LG5UQntuD8iGqZ8FWDDC2SS9DKmi4","timestamp":"2019-12-14T13:28:43Z","validation_pass":4,"operations_hash":"LLoaDd6G4Gre7N6QQDFdJmmnn2Mo4dzKfSrehQoBLkdyPpUijWZui","fitness":["01","0000000000009800"],"context":"CoW4nLbc2FDs2HBRAwoHEFvQurEDondEtG2ToDx2Mp6QZ1rh27my","protocol_data":"0000b1a7b92bfdf70000004e875f142dfd0edbbb8e42bb5d398d3b44e710a4f84a8f8aad121418222785b7f9ba3a45ef24553ef332d052126ad5fdb1e1acbe343aafa22db9e9419f86e109"},"save_point":38913,"caboose":0,"history_mode":"full"} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Change Log 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [v4.0.0] 9 | 10 | EDO Support and Design Improvements 11 | 12 | ### Added 13 | - Returning network response in each RPC function 14 | - Cleaned up testing 15 | - Many new RPC's related to EDO and Delphi 16 | - A new BlockID pattern 17 | - Key management for P256 18 | - Key management for secp256k1 19 | - Forging of script expression for all data types -------------------------------------------------------------------------------- /rpc/.test-fixtures/header.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", 3 | "chain_id": "NetXdQprcVkpaWU", 4 | "hash": "BM6MfqnsWicioPh8YouQYfM3XACDQpYCQsEipQ1iEJm3fg3ymQB", 5 | "level": 1275474, 6 | "proto": 7, 7 | "predecessor": "BL4XzHtmrPXDmTMz83jwSJr8c4tniMyz815AM2NyG8jU8cQyM5F", 8 | "timestamp": "2020-12-26T20:21:24Z", 9 | "validation_pass": 4, 10 | "operations_hash": "LLoaTeyJSHHg8sRU91MqmDZoEbPi8ct5mqgjBjqu3qFEWRRLRzSgD", 11 | "fitness": [ 12 | "01", 13 | "0000000000097652" 14 | ], 15 | "context": "CoV85QYYYKtx9y3awM6WbCrENaM1hCnbhQTq4hEDB5Z7P8utZVbL", 16 | "priority": 0, 17 | "proof_of_work_nonce": "e69b63f191890400", 18 | "signature": "sigjuwvbom3cYgJ1LorhjggU7pcu4tAVeftcBJExTHe2U9w1cGuR5HyjM67VPNGPn77qPLGdNs2ZYxnajTcPyYpVTLarDkdK" 19 | } -------------------------------------------------------------------------------- /rpc/.test-fixtures/frozen_balance_by_cycle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cycle": 304, 4 | "deposit": "22656000000", 5 | "fees": "501356", 6 | "rewards": "797916666" 7 | }, 8 | { 9 | "cycle": 305, 10 | "deposit": "20352000000", 11 | "fees": "74701", 12 | "rewards": "725833332" 13 | }, 14 | { 15 | "cycle": 306, 16 | "deposit": "19584000000", 17 | "fees": "412373", 18 | "rewards": "530416665" 19 | }, 20 | { 21 | "cycle": 307, 22 | "deposit": "21888000000", 23 | "fees": "73308", 24 | "rewards": "694999999" 25 | }, 26 | { 27 | "cycle": 308, 28 | "deposit": "20608000000", 29 | "fees": "72916", 30 | "rewards": "577083332" 31 | }, 32 | { 33 | "cycle": 309, 34 | "deposit": "11712000000", 35 | "fees": "27392", 36 | "rewards": "318750000" 37 | } 38 | ] -------------------------------------------------------------------------------- /rpc/.test-fixtures/constants.json: -------------------------------------------------------------------------------- 1 | {"proof_of_work_nonce_size":8,"nonce_length":32,"max_revelations_per_block":32,"max_operation_data_length":16384,"max_proposals_per_delegate":20,"preserved_cycles":5,"blocks_per_cycle":4096,"blocks_per_commitment":32,"blocks_per_roll_snapshot":256,"blocks_per_voting_period":32768,"time_between_blocks":["60","40"],"endorsers_per_block":32,"hard_gas_limit_per_operation":"1040000","hard_gas_limit_per_block":"10400000","proof_of_work_threshold":"70368744177663","tokens_per_roll":"8000000000","michelson_maximum_type_size":1000,"seed_nonce_revelation_tip":"125000","origination_size":257,"block_security_deposit":"512000000","endorsement_security_deposit":"64000000","baking_reward_per_endorsement":["1250000","187500"],"endorsement_reward":["1250000","833333"],"cost_per_byte":"1000","hard_storage_limit_per_operation":"60000","test_chain_duration":"1966080","quorum_min":2000,"quorum_max":7000,"min_proposal_quorum":500,"initial_endorsers":24,"delay_per_missing_endorsement":"8"} -------------------------------------------------------------------------------- /rpc/.test-fixtures/delegated_contracts.json: -------------------------------------------------------------------------------- 1 | ["tz1fjKL11UAE6DywFh8cN4hmPnQoGzx5WED4","tz1NiLdRoJSubtSinLsE87YpQb2hapjS3Nt8","tz1U4HfT4FPq5BVra6JqGjEzSKnDmECpNB4G","tz1YGLnq1Ls4W3rPanAvCvmcuQ1H5rffnc2V","tz1VG1fQCc59cMnL1g66qVFc5VUACvitvhjk","tz1gwhmUXZ5WU6XRWF5VbKLe1YbT3hcXut4t","tz1SuN5H7coasZuSCHwQxfjoYD897XexGd5Z","tz1Wkq6KDXFr8R1Pfc9qsFKNq1cGZWxsDW8M","tz1YVZtEHtGprtVZtSC342pn5AeskibJ8U5Q","tz1KgZycAC7vvqsFoQAyYufCU6woQDQ7kji3","tz1XN5zXpiKrFae9KE8pLkcSeDhvLxkBSM9d","tz1NSpdiL4LZnsgCwRHhwhY8hfXbTWv1P9m1","tz1PR69DJGUbVkk4bUWJP6mNsQo8EomwfAry","tz1e7axDeFykoUUQUnb64V7xjKrXaCBBPH97","tz1YX4g6wzjKPRz7wm3punsAb5XbTuuq9xV6","tz1LjkdWbWG6YTVsJrdUNFDS6MDksLFzpX84","tz1cwxtKJNuxsb4qBR9gUcBUJUnZc35G6FXe","tz1SMGkowfL5BtWAyr6WrTbMKpcR1SR5Rx3A","tz1N9HduDLV88e881wVZ9QVmvCRTePhTChnP","tz1VxYYPquaFGcDaRUAL4HEbmKVsJCLnb5hZ","tz1NXk3MtEs7dAkHpVPvnfNp4BsJYeXF6VxX","tz1XMPEFYLbwTinPZwVYPy2ctgB8qqrqg1fu","tz1TwuTmmYZkCx8Ws1LRfEGkKUU9ZK2ovdQ7","tz1LeaLL9V2GtRSQy9MeecudKtmKKfrhx2nA","tz1MorwRuLTAFz3c2BDVfAnCCJ4LcaPn3Yhh","tz1bsAUiZR6CBsxDYVqr5vvzRvWAanunwXeC"] -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 DefinitelyNotAGoat/MagicAglet 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /keys/signature.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/goat-systems/go-tezos/v4/internal/crypto" 8 | ) 9 | 10 | // Signature represents the signature of an operation 11 | type Signature struct { 12 | Bytes []byte 13 | prefix []byte 14 | } 15 | 16 | // ToBytes returns the signature as bytes 17 | func (s *Signature) ToBytes() []byte { 18 | return s.Bytes 19 | } 20 | 21 | // ToBase58 returns the signature as a base58 encoded string with the correct prefix 22 | func (s *Signature) ToBase58() string { 23 | return crypto.B58cencode(s.Bytes, s.prefix) 24 | } 25 | 26 | // ToHex returns the signature encoded to hex 27 | func (s *Signature) ToHex() string { 28 | return hex.EncodeToString(s.Bytes) 29 | } 30 | 31 | // AppendToHex takes a hex encoded message and adds the signature to it for injection 32 | func (s *Signature) AppendToHex(msg string) string { 33 | return fmt.Sprintf("%s%s", msg, s.ToHex()) 34 | } 35 | 36 | // AppendToBytes takes a bytes message and adds the signature to it for injection 37 | func (s *Signature) AppendToBytes(msg []byte) []byte { 38 | return append(msg, s.Bytes...) 39 | } 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME := "go-tezos" 2 | VERSION := "v3.0.0" 3 | PKG := "github.com/goat-systems/$(PROJECT_NAME)" 4 | PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/) 5 | GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go) 6 | 7 | .PHONY: dep clean test coverage coverhtml lint 8 | 9 | checks: fmt lint staticcheck race ## Runs all quality checks 10 | 11 | lint: ## Lint the files 12 | @golint -set_exit_status ${PKG_LIST} 13 | 14 | staticcheck: ## Static check the files 15 | @staticcheck ${PKG_LIST} 16 | 17 | fmt: ## Static check the files 18 | @go fmt ${PKG_LIST} 19 | 20 | test: ## Run unittests 21 | @go test -v ${PKG_LIST} 22 | 23 | test-integration: ## Run unit tests and integration tests 24 | @go test -v --tags=integration ${PKG_LIST} 25 | 26 | race: ## Run data race detector 27 | @go test -race -v ${PKG_LIST} 28 | 29 | dep: ## Get the dependencies 30 | @go get -u golang.org/x/lint/golint 31 | @go get -u honnef.co/go/tools/... 32 | 33 | clean: ## Remove previous build 34 | @rm -f $(PROJECT_NAME) 35 | 36 | help: ## Display this help screen 37 | @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 38 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/operation_hashes.json: -------------------------------------------------------------------------------- 1 | [["ontoZH3KLbMdNJU6HfGakKos1pcBLEQS55Ux3ZCapHWGpL4v17N","ooWFa5HJd9tUiHfPyVoqdksspRzBgW1kC8AjoAFWZhrn2v54wNY","opU7NcXQQAVPagXwVrKm55MDVsGfvQSY2jTy71bSwxsnpC2YHu4","oogn9w74HF6uUM5ore3jKcrt44Bq7yydReug2z8A6KVH7EsQ4F9","oo3AMhcBR5gDQwzbcSoxFb4sbeZnNUx4rkYmcMrCXi2Fd5DBzrp","opPwkg94nxvB9NCnyRBKk97KQG2szDFM43mYnqwNJmCNAcUhHKZ","onkGSewfFpoVRFKdppdmfAxL3bHbB3A7K1kCmjVLqchNdHN3kUd","opLTUzonqYszrEPNpt44jgX2HEAH9d9W4qg7gFSmcPSyj97Mabi","ooCakxsfw5zzKLUfg8kyhAzvMx9C6QUSCrXPfyXTsnCatQvM83Q","onyJwWxjxi8zGpUTma9WLZurNDi4729VZ7dhYL9wngbSeS4MWVJ","ooL81wXS6x7ytB1FpERto1XGeKaHkwPPC3TYEXCQ5QLPos2Jzco","opQdyNZ5sw5SBtg242YyDcZiUz2YkZE4pC6bBPuFZNr6g916jZw","op5Qp1RdDL5vVaK8o4ZqFYURpDpwCK3ihwjuh2zf7cgZ1JtCCrv","op4o11R1FD7MGKwmmc2qvyQ2smeCo3tLNLZpJg5R57UJfUU1tkx","opFCd8zmhpsiZVTJL7RSjWvdirA6LTgJcvqMsx5zEqjpeFBjoAU","opCrac8vD2KZYVL98ceU57yoYkCAxhgdinbAxeSU7aPDSju2JKZ","opG6g7G9DGcH6CRUNTwaWbWXTUk6D6f7DgfBFfdUaEozBFTeKY8","onxWG7riay2KeUJJgDSv1WMTYDAEPmF2sVy9jBXLCfxv6vyXWmp","opJjApYUhGq8pSviguzQnAhaHYSizVy7GnPCAFDEQTSWKExnQD9","ooKP1F1eauEvpQukDq6z9YNokSkmHa6dXG5Xz2AaRExmzozJxQH","ootxuD9PRsqJ86kS1WWiqn4GJvnS8nNZBssCCVtWeEtRjtPtW9G","onqLc8fpBApf91CZNJEUmCNgeM2Txy3BeyqtGqnmYHKPqiAM29R","ontZk71Uq4b27BQxuNm4nQdJqHz1DTKe6gEB4eY2M6x42PfYHXU"],[],[],["opZem7DfW59FykWV7xBT3ePTHKuAaRtG5PW2Gq3MkU6wFp4SLGX","oorRiNVX4To8aQVAJce7sDVuEEAofuXoRvBBGdr9um98vCJeXan","ooLiDctxaht3kRoEsZC4Q5HKa2ZkqcVMakPcUwMYa5FNmSZ5bQP","oo2URxWsZoxPaMusaxiScJdAVZTbEHcSgJNyMzsf8CLwC478uEz"]] -------------------------------------------------------------------------------- /rpc/client_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/goat-systems/go-tezos/v4/rpc" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_New(t *testing.T) { 13 | expectedConstants := getResponse(constants).(rpc.Constants) 14 | 15 | cases := []struct { 16 | name string 17 | inputHandler http.Handler 18 | wantErr bool 19 | wantConstants *rpc.Constants 20 | }{ 21 | { 22 | "Successful", 23 | newConstantsMock().handler(readResponse(constants), blankHandler), 24 | false, 25 | &expectedConstants, 26 | }, 27 | { 28 | "fails to fetch constants", 29 | newConstantsMock().handler([]byte(`junk`), blankHandler), 30 | true, 31 | nil, 32 | }, 33 | } 34 | 35 | for _, tt := range cases { 36 | t.Run(tt.name, func(t *testing.T) { 37 | server := httptest.NewServer(tt.inputHandler) 38 | defer server.Close() 39 | 40 | r, err := rpc.New(server.URL) 41 | checkErr(t, tt.wantErr, "", err) 42 | if err == nil { 43 | assert.Equal(t, *tt.wantConstants, r.CurrentContstants()) 44 | } 45 | }) 46 | } 47 | } 48 | func Test_SetConstants(t *testing.T) { 49 | r := rpc.Client{} 50 | 51 | var constants rpc.Constants 52 | r.SetConstants(constants) 53 | 54 | assert.Equal(t, constants, r.CurrentContstants()) 55 | } 56 | 57 | func gtGoldenHTTPMock(next http.Handler) http.Handler { 58 | var constantsMock constantsHandlerMock 59 | return constantsMock.handler( 60 | readResponse(constants), 61 | next, 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /keys/curve.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ECKind is the key type 8 | type ECKind string 9 | 10 | const ( 11 | // Ed25519 https://tools.ietf.org/html/rfc8032 12 | Ed25519 ECKind = "Ed25519" 13 | // Secp256k1 https://tools.ietf.org/html/rfc4492 14 | Secp256k1 ECKind = "Secp256k1" 15 | // NistP256 https://tools.ietf.org/html/rfc5656 16 | NistP256 ECKind = "NistP256" 17 | ) 18 | 19 | type iCurve interface { 20 | addressPrefix() []byte 21 | publicKeyPrefix() []byte 22 | privateKeyPrefix() []byte 23 | signaturePrefix() []byte 24 | getECKind() ECKind 25 | getPrivateKey(v []byte) []byte 26 | getPublicKey(privateKey []byte) ([]byte, error) 27 | sign(msg []byte, privateKey []byte) (Signature, error) 28 | } 29 | 30 | func getCurve(kind ECKind) iCurve { 31 | if kind == Secp256k1 { 32 | return &secp256k1Curve{} 33 | } else if kind == NistP256 { 34 | return &nistP256Curve{} 35 | } 36 | 37 | return &ed25519Curve{} 38 | } 39 | 40 | func getCurveByPrefix(prefix string) (iCurve, error) { 41 | if prefix == "edpk" || prefix == "edsk" || prefix == "tz1" || prefix == "edesk" || prefix == "edsig" { 42 | return &ed25519Curve{}, nil 43 | } 44 | 45 | if prefix == "sppk" || prefix == "spsk" || prefix == "tz2" || prefix == "spesk" || prefix == "spsig" { 46 | return &secp256k1Curve{}, nil 47 | } 48 | 49 | if prefix == "p2pk" || prefix == "p2sk" || prefix == "tz3" || prefix == "p2esk" || prefix == "p2sig" { 50 | return &nistP256Curve{}, nil 51 | } 52 | 53 | return nil, fmt.Errorf("failed to find curve with prefix '%s'", prefix) 54 | } 55 | -------------------------------------------------------------------------------- /rpc/helpers_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package rpc_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/goat-systems/go-tezos/v4/rpc" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_Integration_BakingRights(t *testing.T) { 13 | r, err := rpc.New(HOST) 14 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 15 | t.FailNow() 16 | } 17 | 18 | if _, _, err := r.BakingRights(rpc.BakingRightsInput{ 19 | BlockID: &rpc.BlockIDHead{}, 20 | }); err != nil { 21 | t.FailNow() 22 | } 23 | } 24 | 25 | func Test_Integration_CompletePrefix(t *testing.T) { 26 | r, err := rpc.New(HOST) 27 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 28 | t.FailNow() 29 | } 30 | 31 | if _, _, err := r.CompletePrefix(rpc.CompletePrefixInput{ 32 | BlockID: &rpc.BlockIDHead{}, 33 | Prefix: "tz1", 34 | }); err != nil { 35 | t.FailNow() 36 | } 37 | } 38 | 39 | func Test_Integration_CurrentLevel(t *testing.T) { 40 | r, err := rpc.New(HOST) 41 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 42 | t.FailNow() 43 | } 44 | 45 | if _, _, err := r.CurrentLevel(rpc.CurrentLevelInput{ 46 | BlockID: &rpc.BlockIDHead{}, 47 | }); err != nil { 48 | t.FailNow() 49 | } 50 | } 51 | 52 | func Test_Integration_EndorsingRights(t *testing.T) { 53 | r, err := rpc.New(HOST) 54 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 55 | t.FailNow() 56 | } 57 | 58 | if _, _, err := r.EndorsingRights(rpc.EndorsingRightsInput{ 59 | BlockID: &rpc.BlockIDHead{}, 60 | }); err != nil { 61 | t.FailNow() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/preapply_operations.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "contents":[ 4 | { 5 | "kind":"transaction", 6 | "source":"tz1W3HW533csCBLor4NPtU79R2TT2sbKfJDH", 7 | "fee":"3000", 8 | "counter":"1263232", 9 | "gas_limit":"20000", 10 | "storage_limit":"0", 11 | "amount":"50", 12 | "destination":"tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 13 | "metadata":{ 14 | "balance_updates":[ 15 | { 16 | "kind":"contract", 17 | "contract":"tz1W3HW533csCBLor4NPtU79R2TT2sbKfJDH", 18 | "change":"-3000" 19 | }, 20 | { 21 | "kind":"freezer", 22 | "category":"fees", 23 | "delegate":"tz1Ke2h7sDdakHJQh8WX4Z372du1KChsksyU", 24 | "cycle":229, 25 | "change":"3000" 26 | } 27 | ], 28 | "operation_result":{ 29 | "status":"applied", 30 | "balance_updates":[ 31 | { 32 | "kind":"contract", 33 | "contract":"tz1W3HW533csCBLor4NPtU79R2TT2sbKfJDH", 34 | "change":"-50" 35 | }, 36 | { 37 | "kind":"contract", 38 | "contract":"tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 39 | "change":"50" 40 | } 41 | ], 42 | "consumed_gas":"10207" 43 | } 44 | } 45 | } 46 | ], 47 | "signature":"edsig...." 48 | } 49 | ] -------------------------------------------------------------------------------- /keys/ed25519.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "crypto/ed25519" 5 | 6 | "github.com/pkg/errors" 7 | "golang.org/x/crypto/blake2b" 8 | ) 9 | 10 | var _ iCurve = &ed25519Curve{} 11 | 12 | // https://tools.ietf.org/html/rfc8032 13 | type ed25519Curve struct{} 14 | 15 | func (e *ed25519Curve) addressPrefix() []byte { 16 | return []byte{6, 161, 159} 17 | } 18 | 19 | func (e *ed25519Curve) publicKeyPrefix() []byte { 20 | return []byte{13, 15, 37, 217} 21 | } 22 | 23 | func (e *ed25519Curve) privateKeyPrefix() []byte { 24 | return []byte{43, 246, 78, 7} 25 | } 26 | 27 | func (e *ed25519Curve) signaturePrefix() []byte { 28 | return []byte{9, 245, 205, 134, 18} 29 | } 30 | 31 | func (e *ed25519Curve) getECKind() ECKind { 32 | return Ed25519 33 | } 34 | 35 | func (e *ed25519Curve) getPrivateKey(v []byte) []byte { 36 | return ed25519.NewKeyFromSeed(v[:32]) 37 | } 38 | 39 | func (e *ed25519Curve) getPublicKey(privateKey []byte) ([]byte, error) { 40 | pubKey, ok := ed25519.PrivateKey(privateKey).Public().(ed25519.PublicKey) 41 | if !ok { 42 | return []byte{}, errors.New("failed to cast crypto.PublicKey to ed25519.PublicKey") 43 | } 44 | 45 | return pubKey, nil 46 | } 47 | 48 | func (e *ed25519Curve) sign(msg []byte, privateKey []byte) (Signature, error) { 49 | hash, err := blake2b.New(32, []byte{}) 50 | if err != nil { 51 | return Signature{}, err 52 | } 53 | 54 | i, err := hash.Write(msg) 55 | if err != nil { 56 | return Signature{}, errors.Wrap(err, "failed to sign operation bytes") 57 | } 58 | if i != len(msg) { 59 | return Signature{}, errors.Errorf("failed to sign operation: generic hash length %d does not match bytes length %d", i, len(msg)) 60 | } 61 | 62 | return Signature{ 63 | Bytes: ed25519.Sign(ed25519.PrivateKey(privateKey), hash.Sum([]byte{})), 64 | prefix: e.signaturePrefix(), 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /keys/pubKey.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | tzcrypt "github.com/goat-systems/go-tezos/v4/internal/crypto" 5 | "github.com/pkg/errors" 6 | "golang.org/x/crypto/blake2b" 7 | ) 8 | 9 | // PubKey is the public key to a Tezos Wallet 10 | type PubKey struct { 11 | curve iCurve 12 | pubKey []byte 13 | address string 14 | } 15 | 16 | func newPubKey(v []byte, kind ECKind) (PubKey, error) { 17 | if len(v) < 32 { 18 | return PubKey{}, errors.New("failed to import pub key") 19 | } 20 | 21 | curve := getCurve(kind) 22 | pk, err := curve.getPublicKey(v) 23 | if err != nil { 24 | return PubKey{}, errors.Wrap(err, "failed to import pub key") 25 | } 26 | 27 | hash, err := blake2b.New(20, []byte{}) 28 | if err != nil { 29 | return PubKey{}, errors.Wrapf(err, "failed to import pub key: failed to generate public hash from public key %s", string(pk)) 30 | } 31 | _, err = hash.Write(pk) 32 | if err != nil { 33 | return PubKey{}, errors.Wrapf(err, "failed to import pub key: failed to generate public hash from public key %s", string(pk)) 34 | } 35 | 36 | return PubKey{ 37 | curve: curve, 38 | pubKey: pk, 39 | address: tzcrypt.B58cencode(hash.Sum(nil), curve.addressPrefix()), 40 | }, nil 41 | } 42 | 43 | // GetBytes will return the raw bytes of the public key 44 | func (p *PubKey) GetBytes() []byte { 45 | return p.pubKey 46 | } 47 | 48 | /* 49 | GetPublicKey will return the base58 encoded key with the private key prefix. 50 | Example: 51 | edskRpfRbhVr7SjmVpMK1kzTDrSzuCKroxjQAsfJn94X7LgbpqJLvRDHfNHFT9KbCZAXVVhMmkQGz4APscezMbJFov5ZNPSY9H 52 | */ 53 | func (p *PubKey) GetPublicKey() string { 54 | return tzcrypt.B58cencode(p.pubKey, p.curve.publicKeyPrefix()) 55 | } 56 | 57 | /* 58 | GetAddress will public key hash (address) of the public key. 59 | Example: 60 | tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo 61 | */ 62 | func (p *PubKey) GetAddress() string { 63 | return p.address 64 | } 65 | -------------------------------------------------------------------------------- /rpc/votes_integration_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/goat-systems/go-tezos/v4/rpc" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | const HOST = "https://mainnet-tezos.giganode.io" 11 | 12 | func Test_Integration_BallotList(t *testing.T) { 13 | r, err := rpc.New(HOST) 14 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 15 | t.FailNow() 16 | } 17 | 18 | if _, _, err := r.BallotList(&rpc.BlockIDHead{}); err != nil { 19 | t.FailNow() 20 | } 21 | } 22 | 23 | func Test_Integration_Ballots(t *testing.T) { 24 | r, err := rpc.New(HOST) 25 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 26 | t.FailNow() 27 | } 28 | 29 | if _, _, err := r.Ballots(&rpc.BlockIDHead{}); err != nil { 30 | t.FailNow() 31 | } 32 | } 33 | 34 | func Test_Integration_CurrentPeriodKind(t *testing.T) { 35 | r, err := rpc.New(HOST) 36 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 37 | t.FailNow() 38 | } 39 | 40 | if _, _, err := r.CurrentPeriodKind(&rpc.BlockIDHead{}); err != nil { 41 | t.FailNow() 42 | } 43 | } 44 | 45 | func Test_Integration_CurrentProposal(t *testing.T) { 46 | r, err := rpc.New(HOST) 47 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 48 | t.FailNow() 49 | } 50 | 51 | if _, _, err := r.CurrentProposal(&rpc.BlockIDHead{}); err != nil { 52 | t.FailNow() 53 | } 54 | } 55 | 56 | func Test_Integration_CurrentQuorum(t *testing.T) { 57 | r, err := rpc.New(HOST) 58 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 59 | t.FailNow() 60 | } 61 | 62 | if _, _, err := r.CurrentQuorum(&rpc.BlockIDHead{}); err != nil { 63 | t.FailNow() 64 | } 65 | } 66 | 67 | func Test_Integration_Listings(t *testing.T) { 68 | r, err := rpc.New(HOST) 69 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 70 | t.FailNow() 71 | } 72 | 73 | if _, _, err := r.Listings(&rpc.BlockIDHead{}); err != nil { 74 | t.FailNow() 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /example/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "os" 7 | "strconv" 8 | 9 | "github.com/goat-systems/go-tezos/v4/forge" 10 | "github.com/goat-systems/go-tezos/v4/keys" 11 | "github.com/goat-systems/go-tezos/v4/rpc" 12 | ) 13 | 14 | func main() { 15 | key, err := keys.FromEncryptedSecret("edesk...", "password") 16 | if err != nil { 17 | fmt.Printf("failed to import keys: %s\n", err.Error()) 18 | os.Exit(1) 19 | } 20 | 21 | client, err := rpc.New("http://tezos_rpc_addr") 22 | if err != nil { 23 | fmt.Printf("failed to initialize rpc client: %s\n", err.Error()) 24 | os.Exit(1) 25 | } 26 | 27 | resp, counter, err := client.ContractCounter(rpc.ContractCounterInput{ 28 | BlockID: &rpc.BlockIDHead{}, 29 | ContractID: key.PubKey.GetAddress(), 30 | }) 31 | if err != nil { 32 | fmt.Printf("failed to get (%s) counter: %s\n", resp.Status(), err.Error()) 33 | os.Exit(1) 34 | } 35 | counter++ 36 | 37 | big.NewInt(0).SetString("10000000000000000000000000000", 10) 38 | 39 | transaction := rpc.Transaction{ 40 | Source: key.PubKey.GetPublicKey(), 41 | Fee: "2941", 42 | GasLimit: "26283", 43 | Counter: strconv.Itoa(counter), 44 | Amount: "0", 45 | Destination: "", 46 | } 47 | 48 | resp, head, err := client.Block(&rpc.BlockIDHead{}) 49 | if err != nil { 50 | fmt.Printf("failed to get (%s) head block: %s\n", resp.Status(), err.Error()) 51 | os.Exit(1) 52 | } 53 | 54 | op, err := forge.Encode(head.Hash, transaction.ToContent()) 55 | if err != nil { 56 | fmt.Printf("failed to forge transaction: %s\n", err.Error()) 57 | os.Exit(1) 58 | } 59 | 60 | signature, err := key.SignHex(op) 61 | if err != nil { 62 | fmt.Printf("failed to sign operation: %s\n", err.Error()) 63 | os.Exit(1) 64 | } 65 | 66 | resp, ophash, err := client.InjectionOperation(rpc.InjectionOperationInput{ 67 | Operation: signature.AppendToHex(op), 68 | }) 69 | if err != nil { 70 | fmt.Printf("failed to inject (%s): %s\n", resp.Status(), err.Error()) 71 | os.Exit(1) 72 | } 73 | 74 | fmt.Println(ophash) 75 | } 76 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", 3 | "next_protocol": "PsDELPH1Kxsxt8f9eWbxQeRxkjfbxoqM52jvs5Y5fBxWWh4ifpo", 4 | "test_chain_status": { 5 | "status": "running", 6 | "chain_id": "NetXbKBPL76Ndcj", 7 | "genesis": "BLjzqqoRoF16U6u1BsZupCEYCEsCfgKhtfFWZ2FJr2wdV1uCJPe", 8 | "protocol": "PtEdoTezd3RHSC31mpxxo1npxFjoWWcFgQtxapi51Z8TLu6v6Uq", 9 | "expiration": "2021-01-20T08:08:04Z" 10 | }, 11 | "max_operations_ttl": 60, 12 | "max_operation_data_length": 16384, 13 | "max_block_header_length": 238, 14 | "max_operation_list_length": [ 15 | { 16 | "max_size": 32768, 17 | "max_op": 32 18 | }, 19 | { 20 | "max_size": 32768 21 | }, 22 | { 23 | "max_size": 135168, 24 | "max_op": 132 25 | }, 26 | { 27 | "max_size": 524288 28 | } 29 | ], 30 | "baker": "tz1S8MNvuFEUsWgjHvi3AxibRBf388NhT1q2", 31 | "level": { 32 | "level": 1278525, 33 | "level_position": 1278524, 34 | "cycle": 312, 35 | "cycle_position": 572, 36 | "voting_period": 39, 37 | "voting_period_position": 572, 38 | "expected_commitment": false 39 | }, 40 | "voting_period_kind": "testing", 41 | "nonce_hash": null, 42 | "consumed_gas": "67553623", 43 | "deactivated": [], 44 | "balance_updates": [ 45 | { 46 | "kind": "contract", 47 | "contract": "tz1S8MNvuFEUsWgjHvi3AxibRBf388NhT1q2", 48 | "change": "-512000000" 49 | }, 50 | { 51 | "kind": "freezer", 52 | "category": "deposits", 53 | "delegate": "tz1S8MNvuFEUsWgjHvi3AxibRBf388NhT1q2", 54 | "cycle": 312, 55 | "change": "512000000" 56 | }, 57 | { 58 | "kind": "freezer", 59 | "category": "rewards", 60 | "delegate": "tz1S8MNvuFEUsWgjHvi3AxibRBf388NhT1q2", 61 | "cycle": 312, 62 | "change": "40000000" 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/github.com/goat-systems/go-tezos/v4) 2 | # A Tezos Go Library 3 | 4 | Go Tezos is a GoLang driven library for your Tezos node. This library has received a grant from the Tezos Foundation to ensure it's continuous development through 2020. 5 | 6 | ## Installation 7 | 8 | Get GoTezos 9 | ``` 10 | go get github.com/goat-systems/go-tezos/v4 11 | ``` 12 | 13 | ### Getting A Block 14 | 15 | ``` 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | goTezos "github.com/goat-systems/go-tezos/v4/rpc" 21 | ) 22 | 23 | func main() { 24 | rpc, err := client.New("http://127.0.0.1:8732") 25 | if err != nil { 26 | fmt.Printf("failed tp connect to network: %v", err) 27 | } 28 | 29 | resp, head, err := client.Block(&rpc.BlockIDHead{}) 30 | if err != nil { 31 | fmt.Printf("failed to get (%s) head block: %s\n", resp.Status(), err.Error()) 32 | os.Exit(1) 33 | } 34 | fmt.Println(block) 35 | } 36 | ``` 37 | 38 | ### Getting a Cycle 39 | ``` 40 | resp, cycle, err := rpc.Cycle(50) 41 | if err != nil { 42 | fmt.Printf("failed to get (%s) cycle: %s\n", resp.Status(), err.Error()) 43 | os.Exit(1) 44 | } 45 | fmt.Println(cycle) 46 | ``` 47 | 48 | ### More Examples 49 | You can find more examples by looking through the unit tests and integration tests in each package. [Here](example/transaction/transaction.go) is an example on 50 | how to forge and inject an operation. 51 | 52 | ## Contributing 53 | 54 | ### The Makefile 55 | The makefile is there as a helper to run quality code checks. To run vet and staticchecks please run: 56 | ``` 57 | make checks 58 | ``` 59 | 60 | ## Contributers: A Special Thank You 61 | 62 | * [**BrianBland**](https://github.com/BrianBland) 63 | * [**utdrmac**](https://github.com/utdrmac) 64 | * [**Magic_Gum**](https://github.com/fkbenjamin) 65 | * [**Johann**](https://github.com/tulpenhaendler) 66 | * [**leopoldjoy**](https://github.com/leopoldjoy) 67 | * [**RomarQ**](https://github.com/RomarQ) 68 | * [**surzm**](https://github.com/surzm) 69 | * [**fredcy**](https://github.com/fredcy) 70 | 71 | ## License 72 | 73 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 74 | -------------------------------------------------------------------------------- /keys/integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package keys 4 | 5 | import ( 6 | "os" 7 | "strconv" 8 | "testing" 9 | 10 | "github.com/goat-systems/go-tezos/v4/forge" 11 | "github.com/goat-systems/go-tezos/v4/internal/testutils" 12 | "github.com/goat-systems/go-tezos/v4/rpc" 13 | ) 14 | 15 | func Test_OperationWithKey(t *testing.T) { 16 | type input struct { 17 | sk string 18 | kind ECKind 19 | } 20 | 21 | cases := []struct { 22 | name string 23 | input input 24 | wantErr bool 25 | }{ 26 | { 27 | "is successful Ed25519", 28 | input{ 29 | sk: "edsk2oJWw5CX7Fh3g8QDqtK9CmrvRDDDSeHAPvWPnm7CwD3RfQ1KbK", 30 | kind: Ed25519, 31 | }, 32 | false, 33 | }, 34 | { 35 | "is successful Secp256k1", 36 | input{ 37 | sk: "spsk1WCtWP1fEc4RaE63YK6oUEmbjLK2aTe7LevYSb9Z3zDdtq58wS", 38 | kind: Secp256k1, 39 | }, 40 | false, 41 | }, 42 | } 43 | 44 | for _, tt := range cases { 45 | t.Run(tt.name, func(t *testing.T) { 46 | rpchost := os.Getenv("GOTEZOS_TEST_RPC_HOST") 47 | r, _ := rpc.New(rpchost) 48 | 49 | key, err := FromBase58(tt.input.sk, tt.input.kind) 50 | testutils.CheckErr(t, tt.wantErr, "", err) 51 | 52 | head, _ := r.Head() 53 | 54 | counter, _ := r.Counter(rpc.CounterInput{ 55 | Blockhash: head.Hash, 56 | Address: key.PubKey.address, 57 | }) 58 | 59 | transaction := rpc.Transaction{ 60 | Kind: rpc.TRANSACTION, 61 | Source: key.PubKey.address, 62 | Fee: "2941", 63 | Counter: strconv.Itoa((counter + 1)), 64 | GasLimit: "26283", 65 | Amount: "1", 66 | StorageLimit: "0", 67 | Destination: "tz1RomaiWJV3NFDZWTMVR2aEeHknsn3iF5Gi", 68 | } 69 | 70 | op, err := forge.Encode(head.Hash, transaction.ToContent()) 71 | testutils.CheckErr(t, tt.wantErr, "", err) 72 | 73 | sig, err := key.SignHex(op) 74 | testutils.CheckErr(t, tt.wantErr, "", err) 75 | 76 | _, err = r.PreapplyOperations(rpc.PreapplyOperationsInput{ 77 | Blockhash: head.Hash, 78 | Operations: []rpc.Operations{ 79 | { 80 | Protocol: head.Protocol, 81 | Branch: head.Hash, 82 | Contents: rpc.Contents{transaction.ToContent()}, 83 | Signature: sig.ToBase58(), 84 | }, 85 | }, 86 | }) 87 | testutils.CheckErr(t, tt.wantErr, "", err) 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/endorsing_rights.json: -------------------------------------------------------------------------------- 1 | [{"level":822092,"delegate":"tz3bvNMQ95vfAYtG8193ymshqjSvmxiCUuR5","slots":[25],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3WMqdzXqRWXwyvj5Hp2H7QEepaUuS7vd9K","slots":[20,17],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN","slots":[15,11],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3UoffC7FG7zfpmvmjUmUeAaHvzdcUvAj6r","slots":[7],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9","slots":[30,26,13,12,8],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3RB4aoyjov4KEVRbuhvQ1CKJgBJMWhaeB8","slots":[10,5],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz3NExpXn9aPNZPorRE4SdjJ2RGrfbJgMAaV","slots":[19],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz2BzJTyoQp8fNbfhWD4YQgH9JJHDgSGzpdG","slots":[1],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1isXamBXpTUgbByQ6gXgZQg4GWNW7r6rKE","slots":[21],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1irJKkXS2DBWkU1NnmFQx1c1L7pbGg4yhk","slots":[24,14,0],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA","slots":[28],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1gfArv665EUkSg2ojMBzcbfwuPxAvqPvjo","slots":[23],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1eEnQhbwf6trb8Q8mPb2RaPkNk2rN7BKi8","slots":[22],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1Z2jXfEXL7dXhs6bsLmyLFLfmAkXBzA9WE","slots":[4],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1W5VkdB5s7ENMESVBtwyt9kyvLqPcUczRT","slots":[16,9],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1UuQ4HWDu3ALNRgAq94dX9MhqhQhnuY3gC","slots":[6],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1TzaNn7wSQSP5gYPXCnNzBCpyMiidCq1PX","slots":[2],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1Tnjaxk6tbAeC2TmMApPh8UsrEVQvhHvx5","slots":[3],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1S8MNvuFEUsWgjHvi3AxibRBf388NhT1q2","slots":[18],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1LmaFsWRkjr7QMCx5PtV6xTUz3AmEpKQiF","slots":[31],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1Ldzz6k1BHdhuKvAtMRX7h5kJSMHESMHLC","slots":[27],"estimated_time":"2020-02-13T02:46:51Z"},{"level":822092,"delegate":"tz1LBEKXaxQbd5Gtzbc1ATCwc3pppu81aWGc","slots":[29],"estimated_time":"2020-02-13T02:46:51Z"}] -------------------------------------------------------------------------------- /keys/nistP256.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "math/big" 8 | 9 | "github.com/pkg/errors" 10 | "golang.org/x/crypto/blake2b" 11 | ) 12 | 13 | var _ iCurve = &nistP256Curve{} 14 | 15 | type nistP256Curve struct{} 16 | 17 | func (n *nistP256Curve) addressPrefix() []byte { 18 | return []byte{6, 161, 164} 19 | } 20 | 21 | func (n *nistP256Curve) publicKeyPrefix() []byte { 22 | return []byte{3, 178, 139, 127} 23 | } 24 | 25 | func (n *nistP256Curve) privateKeyPrefix() []byte { 26 | return []byte{16, 81, 238, 189} 27 | } 28 | 29 | func (n *nistP256Curve) signaturePrefix() []byte { 30 | return []byte{54, 240, 44, 52} 31 | } 32 | 33 | func (n *nistP256Curve) getECKind() ECKind { 34 | return NistP256 35 | } 36 | 37 | func (n *nistP256Curve) getPrivateKey(v []byte) []byte { 38 | return v[:32] 39 | } 40 | 41 | func (n *nistP256Curve) getPublicKey(privateKey []byte) ([]byte, error) { 42 | var privKey ecdsa.PrivateKey 43 | privKey.D = new(big.Int).SetBytes(privateKey) 44 | privKey.PublicKey.Curve = elliptic.P256() 45 | privKey.PublicKey.X, privKey.PublicKey.Y = privKey.PublicKey.Curve.ScalarBaseMult(privKey.D.Bytes()) 46 | 47 | var pref []byte 48 | if privKey.PublicKey.Y.Bytes()[31]%2 == 0 { 49 | pref = []byte{2} 50 | } else { 51 | pref = []byte{3} 52 | } 53 | 54 | // 32 padded 0's 55 | pad := []byte{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, 0} 56 | pad = append(pad, privKey.PublicKey.X.Bytes()...) 57 | 58 | return append(pref, pad[len(pad)-32:]...), nil 59 | } 60 | 61 | func (n *nistP256Curve) sign(msg []byte, privateKey []byte) (Signature, error) { 62 | hash, err := blake2b.New(32, []byte{}) 63 | if err != nil { 64 | return Signature{}, err 65 | } 66 | 67 | i, err := hash.Write(msg) 68 | if err != nil { 69 | return Signature{}, errors.Wrap(err, "failed to sign operation bytes") 70 | } 71 | if i != len(msg) { 72 | return Signature{}, errors.Errorf("failed to sign operation: generic hash length %d does not match bytes length %d", i, len(msg)) 73 | } 74 | 75 | var privKey ecdsa.PrivateKey 76 | privKey.D = new(big.Int).SetBytes(privateKey) 77 | privKey.PublicKey.Curve = elliptic.P256() 78 | privKey.PublicKey.X, privKey.PublicKey.Y = privKey.PublicKey.Curve.ScalarBaseMult(privKey.D.Bytes()) 79 | 80 | r, ss, err := ecdsa.Sign(rand.Reader, &privKey, hash.Sum([]byte{})) 81 | if err != nil { 82 | return Signature{}, err 83 | } 84 | 85 | signature := append(r.Bytes(), ss.Bytes()...) 86 | 87 | return Signature{ 88 | Bytes: signature, 89 | prefix: n.signaturePrefix(), 90 | }, nil 91 | } 92 | -------------------------------------------------------------------------------- /keys/secp256k1.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/rand" 6 | "math/big" 7 | 8 | ethcrypto "github.com/ethereum/go-ethereum/crypto" 9 | "github.com/pkg/errors" 10 | "golang.org/x/crypto/blake2b" 11 | ) 12 | 13 | var _ iCurve = &secp256k1Curve{} 14 | 15 | type secp256k1Curve struct{} 16 | 17 | func order() *big.Int { 18 | i, _ := big.NewInt(0).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16) 19 | return i 20 | } 21 | 22 | func maxS() *big.Int { 23 | i, _ := big.NewInt(0).SetString("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0", 16) 24 | return i 25 | } 26 | 27 | func (s *secp256k1Curve) addressPrefix() []byte { 28 | return []byte{6, 161, 161} 29 | } 30 | 31 | func (s *secp256k1Curve) publicKeyPrefix() []byte { 32 | return []byte{3, 254, 226, 86} 33 | } 34 | 35 | func (s *secp256k1Curve) privateKeyPrefix() []byte { 36 | return []byte{17, 162, 224, 201} 37 | } 38 | 39 | func (s *secp256k1Curve) signaturePrefix() []byte { 40 | return []byte{13, 115, 101, 19, 63} 41 | } 42 | 43 | func (s *secp256k1Curve) getECKind() ECKind { 44 | return Secp256k1 45 | } 46 | 47 | func (s *secp256k1Curve) getPrivateKey(v []byte) []byte { 48 | return v[:32] 49 | } 50 | 51 | func (s *secp256k1Curve) getPublicKey(privateKey []byte) ([]byte, error) { 52 | privKey, err := ethcrypto.ToECDSA(privateKey) 53 | if err != nil { 54 | return []byte{}, err 55 | } 56 | 57 | var pref []byte 58 | if privKey.PublicKey.Y.Bytes()[31]%2 == 0 { 59 | pref = []byte{2} 60 | } else { 61 | pref = []byte{3} 62 | } 63 | 64 | // 32 padded 0's 65 | pad := []byte{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, 0} 66 | pad = append(pad, privKey.PublicKey.X.Bytes()...) 67 | 68 | return append(pref, pad[len(pad)-32:]...), nil 69 | } 70 | 71 | func (s *secp256k1Curve) sign(msg []byte, privateKey []byte) (Signature, error) { 72 | hash, err := blake2b.New(32, []byte{}) 73 | if err != nil { 74 | return Signature{}, err 75 | } 76 | 77 | i, err := hash.Write(msg) 78 | if err != nil { 79 | return Signature{}, errors.Wrap(err, "failed to sign operation bytes") 80 | } 81 | if i != len(msg) { 82 | return Signature{}, errors.Errorf("failed to sign operation: generic hash length %d does not match bytes length %d", i, len(msg)) 83 | } 84 | 85 | privKey, err := ethcrypto.ToECDSA(privateKey) 86 | if err != nil { 87 | return Signature{}, err 88 | } 89 | 90 | r, ss, err := ecdsa.Sign(rand.Reader, privKey, hash.Sum([]byte{})) 91 | if err != nil { 92 | return Signature{}, err 93 | } 94 | 95 | if ss.Cmp(maxS()) > 0 { 96 | ss = big.NewInt(0).Sub(order(), ss) 97 | } 98 | 99 | signature := append(r.Bytes(), ss.Bytes()...) 100 | return Signature{ 101 | Bytes: signature, 102 | prefix: s.signaturePrefix(), 103 | }, nil 104 | } 105 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/connections.json: -------------------------------------------------------------------------------- 1 | [{"incoming":false,"peer_id":"idsjDeSrQivFbkSmT24cmFn7zaFcHL","id_point":{"addr":"::ffff:51.158.99.28","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idtW4LULbPAaoT5tBzvMJWx94HBkrh","id_point":{"addr":"::ffff:157.230.147.195","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idrZgdkNYrdHn5My4uHxUGxFRLpcNt","id_point":{"addr":"::ffff:46.245.179.161","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idtFhWy1vk3FgshGoKomYq12M7n7FZ","id_point":{"addr":"::ffff:88.198.23.236","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idrAf4BTfTf511fiXWYk1yKHQ2WGzW","id_point":{"addr":"::ffff:34.90.171.21","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idtXDzsjXynvPDq6iTSK3VdpcqLHKT","id_point":{"addr":"::ffff:54.214.190.58","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idtYrBANfMh8W1WR3KSxzTFXGBh6bS","id_point":{"addr":"::ffff:173.212.230.241","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}},{"incoming":false,"peer_id":"idrMC3YXYWk498kCGan884Hk6FvkKM","id_point":{"addr":"::ffff:34.243.126.77","port":9732},"remote_socket_port":9732,"announced_version":{"chain_name":"TEZOS_MAINNET","distributed_db_version":0,"p2p_version":0},"private":false,"local_metadata":{"disable_mempool":false,"private_node":false},"remote_metadata":{"disable_mempool":false,"private_node":false}}] -------------------------------------------------------------------------------- /internal/crypto/crypto.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "errors" 7 | "math/big" 8 | "reflect" 9 | 10 | "github.com/btcsuite/btcutil/base58" 11 | ) 12 | 13 | const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 14 | 15 | //B58cencode encodes a byte array into base58 with prefix 16 | func B58cencode(payload []byte, prefix []byte) string { 17 | n := make([]byte, (len(prefix) + len(payload))) 18 | copy(n, append(prefix, payload...)) 19 | b58c := Encode(n) 20 | return b58c 21 | } 22 | 23 | // B58cdecode - 24 | func B58cdecode(payload string, prefix []byte) []byte { 25 | b58c, _ := Decode(payload) 26 | return b58c[len(prefix):] 27 | } 28 | 29 | // Encode - 30 | func Encode(dataBytes []byte) string { 31 | 32 | // Performing SHA256 twice 33 | sha256hash := sha256.New() 34 | sha256hash.Write(dataBytes) 35 | middleHash := sha256hash.Sum(nil) 36 | sha256hash = sha256.New() 37 | sha256hash.Write(middleHash) 38 | hash := sha256hash.Sum(nil) 39 | 40 | checksum := hash[:4] 41 | dataBytes = append(dataBytes, checksum...) 42 | 43 | // For all the "00" versions or any prepended zeros as base58 removes them 44 | zeroCount := 0 45 | for _, b := range dataBytes { 46 | if b == 0 { 47 | zeroCount++ 48 | } else { 49 | break 50 | } 51 | } 52 | 53 | // Performing base58 encoding 54 | encoded := base58.Encode(dataBytes) 55 | 56 | for i := 0; i < zeroCount; i++ { 57 | encoded = "1" + encoded 58 | } 59 | 60 | return encoded 61 | } 62 | 63 | // Decode - 64 | func Decode(encoded string) ([]byte, error) { 65 | zeroCount := 0 66 | for i := 0; i < len(encoded); i++ { 67 | if encoded[i] == 49 { 68 | zeroCount++ 69 | } else { 70 | break 71 | } 72 | } 73 | 74 | dataBytes, err := b58decode(encoded) 75 | if err != nil { 76 | return []byte{}, err 77 | } 78 | 79 | if len(dataBytes) <= 4 { 80 | return []byte{}, errors.New("invalid decode length") 81 | } 82 | data, checksum := dataBytes[:len(dataBytes)-4], dataBytes[len(dataBytes)-4:] 83 | 84 | for i := 0; i < zeroCount; i++ { 85 | data = append([]byte{0}, data...) 86 | } 87 | 88 | // Performing SHA256 twice to validate checksum 89 | sha256hash := sha256.New() 90 | sha256hash.Write(data) 91 | middleHash := sha256hash.Sum(nil) 92 | sha256hash = sha256.New() 93 | sha256hash.Write(middleHash) 94 | hash := sha256hash.Sum(nil) 95 | 96 | if !reflect.DeepEqual(checksum, hash[:4]) { 97 | return []byte{}, errors.New("data and checksum don't match") 98 | } 99 | 100 | return data, nil 101 | } 102 | 103 | func b58decode(data string) ([]byte, error) { 104 | decimalData := new(big.Int) 105 | alphabetBytes := []byte(alphabet) 106 | multiplier := big.NewInt(58) 107 | 108 | for _, value := range data { 109 | pos := bytes.IndexByte(alphabetBytes, byte(value)) 110 | if pos == -1 { 111 | return nil, errors.New("character not found in alphabet") 112 | } 113 | decimalData.Mul(decimalData, multiplier) 114 | decimalData.Add(decimalData, big.NewInt(int64(pos))) 115 | } 116 | 117 | return decimalData.Bytes(), nil 118 | } 119 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/live_blocks.json: -------------------------------------------------------------------------------- 1 | [ 2 | "BKieykqqXK96LGJQRrY6x4HfwfeTuvqpLp7hFdoQGtRtaHuqH94", 3 | "BKndphBiqtY6ZjacQPjCXA1BEHdwtQfn1E6rGLj5LnKMLm1BA9s", 4 | "BKnrC9NHKfkCqAMPatTLJ69hVKGNqDgx2qrLn7x7XTGihucowJv", 5 | "BKo7bADFmeT68PUmEVgGUXH7byqGC67w7fisfrqk7XdfN1GGvd4", 6 | "BKrLn4KRiNbba2gRGvDX72LbNhXsdN7E9qPMAwyET6NkkBHsnQF", 7 | "BKrVKEWrq21yvkPpEUYf9UF8CE2vdZxxuiPVVGEmmFwGvdStHAZ", 8 | "BKsqSj4PVPsqaZbnvrao2YedvsWRivy7vqqqjyAhY7vXkYXVLEU", 9 | "BKtgv99GKwnuzxxYvQsvwCfzg6Pnw12AXUZq5bqKgHYvpCn5BPH", 10 | "BKvEs8eQcarx5ATyatL7HLC3p6ysAddcgRUnhAcaREGByzCoTtA", 11 | "BKw62Cdf7SvJtZt2fhiAc75cZSqU9Rvkw3MM6PpZjtVpnSZ77S1", 12 | "BKwVo7b2RKkQM4Zn3AzF8R8Cz6oe3Epf3RNzuEy911SLVJM6JKV", 13 | "BKxZYV6Lbq5VKLNrqjhjbHAkatG9VoZmCNoaVZASwZ43gi1enRp", 14 | "BKyvvE1YWRAPbpWt54cNwR7mozhPAMHBpPZmd4iKp9MB6EXXX1V", 15 | "BL1bb8StXnWwT3H1wyuCmBMdoqKStMm4aLQWyzUCeaiC17UmPg1", 16 | "BL4Yfe3kSXJPTge9uBn2NHgCkpmwVJkRbuFS8RuY58zQSvchBNZ", 17 | "BLASdRSqgqVQ1tRyREyG4Pe2Vkph5DkP4k1jyMvXgpZQJ7xzHDn", 18 | "BLBmbvJ35q2Ad5ySUnhFf5YYV5Ni1FyrabNtLr7XioJdqduTEm3", 19 | "BLCzEJHuMg7xXfBXVrfz8278hoDcdjcRzcRdpyuffrcAQ5LhG26", 20 | "BLDCzxsLwcyPbejqULSsFUcuDwTEdPXEHqxn5pKhHDFYnoF5Fuq", 21 | "BLEwZ4ww7myqHqhUJkayt64FSmkkPJN5hs3mhuTeCzJVAquSy8h", 22 | "BLNNmvGRdHEFSh98EMT3dYH9ZwmfG69oLTzhnvDvbGawZKR1Lqi", 23 | "BLQUoc5PSL4kJSMw3ByC7Hcr5vqxYhRAEJa1YwoL19L2147PcAS", 24 | "BLRFQJr4mW5Uj7cynmBLQD4dDJjKF1df2913rZKKXmVtLsg5nUw", 25 | "BLRq1q6af3Exerf5RXumkihU55GPTfggzwT8FBebdWSiUBTE9r5", 26 | "BLSYioG6ZtqWT3z1yExkxpJpBzM7wafdSXfyaYRmbNt2DBWYqp6", 27 | "BLZtToU2BEMDVqavvNWje8WPZ2Rrn2ggM1CXoNXU3TCCuzhwcyw", 28 | "BLa9YHkZ2o24oFpxM1sdG98Q2vDqkWifj5NDo3UnK5t2hNUuhLd", 29 | "BLaz3KP9RhGa8HQQ8DaEpuKJ1p5AsZgSbnkzFMnR3jmyEQzf96z", 30 | "BLd5afx7FQ9YAVsHgRnBH2xzDxmpp3MgK89W9ZuMArtuZNBq7N5", 31 | "BLd7SB8uerv3LgeTptZb8XxaQD1XU3Ru6vKFifsy6kRoLcBrEf8", 32 | "BLdD329xLqgQTrkNeRkHLWAamCUdJakJZLqMYNvnecPyHx5LBYe", 33 | "BLfccu9hKwhaMkhuPY2rDqNQ2UB5nTYkPVdXDqVoF6aAD9niKRv", 34 | "BLfmDEVeSNWuCoajwpsT3qxe3JnsthgNjAHQd8xSEyVPbKYpjuX", 35 | "BLfpfn6Xt66QHC2oUPLjAwoVYLCTNmbK8tY2yLqtevYTYKidWax", 36 | "BLgZys71Lzt5CUzCgicoxsohfbjpLMyRKD3eBjHSdMyYBQ4fsPK", 37 | "BLhaSsUYw2SXEVBizTzrGKxXriTxLJxoAFAHg3sEQtR2ooaj82W", 38 | "BLjNqF4ah8eNEHkaRcdstwJ6e9NdFqBX6wy77wEZGFEzR18U7uF", 39 | "BLoei3zvn3tubp4XSHipDZpKqUd3CDwSBghAN1q13tMUVtHk1i5", 40 | "BLuYpuUjG9BKN7cTNkqUwAsSXj5NPu5eJiAJ8iqikce4mdLMSFb", 41 | "BLvs3ensgm9FJUErYpiqyDHmVF6FA1BxCmmvCn9UKpz5ZmkBiv7", 42 | "BLwJ1suTemRVAndaSWsQ56svyZcs6xxWNWv57dPKZP78wBffP4f", 43 | "BLwPNyHDFaVEG5SDprbr2Y7vJBn1UiD2UvNNUsTNi7t35qPA6EX", 44 | "BLwcAGz5yVYuWmfu7BdozR31mbx5NRRjeg27QkKWjXqQqZz8umv", 45 | "BLzMEtQRVBZ8Zf53FvBfmUf5YC5VDW7u3aDmc5PUCphnsD8kdaK", 46 | "BLzoWTEwX9A9XH3N9HaK4hAyfoVxBcqajLTQwKeq5CzbBqusE8M", 47 | "BM2yu9ELg7GewLfuqccRepAEheNLdXRZQJxyQ1basK6vsJV3JVE", 48 | "BM3WxnvwaUWi4F1zn2WkxFQZia5o4R2FbC6zM8zZED4huFuNTzn", 49 | "BM75qmX34Sh5P6w49yjazisDmSmCcE84MNgEzhESwy2LVRRFGPW", 50 | "BM9Lw3W1DJcGkB5cLGgn3JpFojJ1CqFJDMZWKqwk6Lwmgw7fW3D", 51 | "BMAK4MAhc2rtpu6oorsbcMTWTpnJFRSccMUQBJ9yfgeKv2nmeD4", 52 | "BMCK9vdjCPAykoeT723dUuE6d2kXEMNVVE6ZQxzAvwxt5RsHtNy", 53 | "BMKaXpUG3GQ9x9qGxqwxn9r2soWyQjALM4xUtMFWoaE7V6GYSn6", 54 | "BMLHQ7Hs46p5Csv85tpkQ9DP9xRvvbLx6faGVgJq9ovHjLTvMKE", 55 | "BMMSUPko9ZCZcYf8nPJYYFkdJb3KrWzY1ZxpWvfNZi9EUxPyVWE", 56 | "BMQdN3hV9EDw6zoerfiSzGZzM1sW2BHUnsaK7hnU5kvfJYEqUig", 57 | "BMQumkJuR1N1kTE5cy4B7o2raqFaSr4SkJWKz8X9DowdiVMeAYc", 58 | "BMSoSYRhfteDbSpkGqHfhnL5LgZxMyX9GgoVJEGY2DGQQMQTxk8", 59 | "BMXeSsXZCRbmgSfHYXPw1wrD3qJTAHMQEkhNDubJXGrjcQ9UwZL", 60 | "BMZRnGyS999QVFZramUReQHx5guH6td1aGVB7nYEE8YeywhRvx8", 61 | "BMb91UQFopEioW3Hfhk58iPotkcWBze81kLayqnCmwHrGvy6vaq", 62 | "BMcY4TDNeSbYvJcqrWJrUng4w5XRJCJrm4U9zaXCkCHKmSbhGHS" 63 | ] -------------------------------------------------------------------------------- /rpc/.test-fixtures/baking_rights.json: -------------------------------------------------------------------------------- 1 | [{"level":732756,"delegate":"tz1LmaFsWRkjr7QMCx5PtV6xTUz3AmEpKQiF","priority":0,"estimated_time":"2019-12-12T11:27:11Z"},{"level":732756,"delegate":"tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN","priority":1,"estimated_time":"2019-12-12T11:27:51Z"},{"level":732756,"delegate":"tz1RCFbB9GpALpsZtu6J58sb74dm8qe6XBzv","priority":2,"estimated_time":"2019-12-12T11:28:31Z"},{"level":732756,"delegate":"tz3RB4aoyjov4KEVRbuhvQ1CKJgBJMWhaeB8","priority":3,"estimated_time":"2019-12-12T11:29:11Z"},{"level":732756,"delegate":"tz3e75hU4EhDU3ukyJueh5v6UvEHzGwkg3yC","priority":4,"estimated_time":"2019-12-12T11:29:51Z"},{"level":732756,"delegate":"tz1eEnQhbwf6trb8Q8mPb2RaPkNk2rN7BKi8","priority":5,"estimated_time":"2019-12-12T11:30:31Z"},{"level":732756,"delegate":"tz1WpeqFaBG9Jm73Dmgqamy8eF8NWLz9JCoY","priority":7,"estimated_time":"2019-12-12T11:31:51Z"},{"level":732756,"delegate":"tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA","priority":8,"estimated_time":"2019-12-12T11:32:31Z"},{"level":732756,"delegate":"tz1LLNkQK4UQV6QcFShiXJ2vT2ELw449MzAA","priority":9,"estimated_time":"2019-12-12T11:33:11Z"},{"level":732756,"delegate":"tz1TRqbYbUf2GyrjErf3hBzgBJPzW8y36qEs","priority":10,"estimated_time":"2019-12-12T11:33:51Z"},{"level":732756,"delegate":"tz2TSvNTh2epDMhZHrw73nV9piBX7kLZ9K9m","priority":11,"estimated_time":"2019-12-12T11:34:31Z"},{"level":732756,"delegate":"tz3bTdwZinP8U1JmSweNzVKhmwafqWmFWRfk","priority":13,"estimated_time":"2019-12-12T11:35:51Z"},{"level":732756,"delegate":"tz1irJKkXS2DBWkU1NnmFQx1c1L7pbGg4yhk","priority":14,"estimated_time":"2019-12-12T11:36:31Z"},{"level":732756,"delegate":"tz3NExpXn9aPNZPorRE4SdjJ2RGrfbJgMAaV","priority":15,"estimated_time":"2019-12-12T11:37:11Z"},{"level":732756,"delegate":"tz1Zhv3RkfU2pHrmaiDyxp7kFZpZrUCu1CiF","priority":17,"estimated_time":"2019-12-12T11:38:31Z"},{"level":732756,"delegate":"tz1WCd2jm4uSt4vntk4vSuUWoZQGhLcDuR9q","priority":18,"estimated_time":"2019-12-12T11:39:11Z"},{"level":732756,"delegate":"tz3WMqdzXqRWXwyvj5Hp2H7QEepaUuS7vd9K","priority":19,"estimated_time":"2019-12-12T11:39:51Z"},{"level":732756,"delegate":"tz1SYq214SCBy9naR6cvycQsYcUGpBqQAE8d","priority":20,"estimated_time":"2019-12-12T11:40:31Z"},{"level":732756,"delegate":"tz1isXamBXpTUgbByQ6gXgZQg4GWNW7r6rKE","priority":21,"estimated_time":"2019-12-12T11:41:11Z"},{"level":732756,"delegate":"tz1iJ4qgGTzyhaYEzd1RnC6duEkLBd1nzexh","priority":22,"estimated_time":"2019-12-12T11:41:51Z"},{"level":732756,"delegate":"tz1Ldzz6k1BHdhuKvAtMRX7h5kJSMHESMHLC","priority":23,"estimated_time":"2019-12-12T11:42:31Z"},{"level":732756,"delegate":"tz3bvNMQ95vfAYtG8193ymshqjSvmxiCUuR5","priority":25,"estimated_time":"2019-12-12T11:43:51Z"},{"level":732756,"delegate":"tz1c3Wh8gNMMsYwZd67JndQpYxdaaPUV27E7","priority":26,"estimated_time":"2019-12-12T11:44:31Z"},{"level":732756,"delegate":"tz1SohptP53wDPZhzTWzDUFAUcWF6DMBpaJV","priority":27,"estimated_time":"2019-12-12T11:45:11Z"},{"level":732756,"delegate":"tz1MXFrtZoaXckE41bjUCSjAjAap3AFDSr3N","priority":29,"estimated_time":"2019-12-12T11:46:31Z"},{"level":732756,"delegate":"tz1ZNWFe3LmEJYTydctcgD6a5Apemwdtimn4","priority":31,"estimated_time":"2019-12-12T11:47:51Z"},{"level":732756,"delegate":"tz1MecudVJnFZN5FSrriu8ULz2d6dDTR7KaM","priority":34,"estimated_time":"2019-12-12T11:49:51Z"},{"level":732756,"delegate":"tz1TNWtofRofCU11YwCNwTMWNFBodYi6eNqU","priority":41,"estimated_time":"2019-12-12T11:54:31Z"},{"level":732756,"delegate":"tz1NpWrAyDL9k2Lmnyxcgr9xuJakbBxdq7FB","priority":44,"estimated_time":"2019-12-12T11:56:31Z"},{"level":732756,"delegate":"tz1TcH4Nb3aHNDJ7CGZhU7jgAK1BkSP4Lxds","priority":48,"estimated_time":"2019-12-12T11:59:11Z"},{"level":732756,"delegate":"tz1bacP88iSnWHAVUBQShtE4ZnUGYHUpGVBM","priority":53,"estimated_time":"2019-12-12T12:02:31Z"},{"level":732756,"delegate":"tz1f3Re8iw6Pt3KMHAvyccHxDU3NuqL95axD","priority":54,"estimated_time":"2019-12-12T12:03:11Z"},{"level":732756,"delegate":"tz1Nn14BBsDULrPXtkM9UQeXaE4iqJhmqmK5","priority":55,"estimated_time":"2019-12-12T12:03:51Z"},{"level":732756,"delegate":"tz1P2Po7YM526ughEsRbY4oR9zaUPDZjxFrb","priority":56,"estimated_time":"2019-12-12T12:04:31Z"},{"level":732756,"delegate":"tz1VoUzbL6X4RdpxwaccV6eQkeqP6b9sUbYa","priority":59,"estimated_time":"2019-12-12T12:06:31Z"}] 2 | -------------------------------------------------------------------------------- /rpc/client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/go-resty/resty/v2" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | // MUTEZ is mutez on the tezos network 14 | const MUTEZ = 1000000 15 | 16 | var ( 17 | regRPCError = regexp.MustCompile(`\s?\{\s?\"(kind|error)\"\s?:\s?\"[^,]+\"\s?,\s?\"(kind|error)"\s?:\s?\"[^}]+\s?\}\s?`) 18 | ) 19 | 20 | /* 21 | Client contains a client (http.Client), network contents, and the host of the node. Gives access to 22 | RPC related functions. 23 | */ 24 | type Client struct { 25 | client *resty.Client 26 | chain string 27 | networkConstants *Constants 28 | host string 29 | } 30 | 31 | /* 32 | Error represents and RPC error 33 | */ 34 | type Error struct { 35 | Kind string `json:"kind"` 36 | Err string `json:"error"` 37 | } 38 | 39 | func (r *Error) Error() string { 40 | return fmt.Sprintf("rpc error (%s): %s", r.Kind, r.Err) 41 | } 42 | 43 | /* 44 | Errors represents multiple RPCError(s).s 45 | */ 46 | type Errors []Error 47 | 48 | type rpcOptions struct { 49 | Key string 50 | Value string 51 | } 52 | 53 | func queryParams(options ...rpcOptions) map[string]string { 54 | m := make(map[string]string) 55 | for _, opt := range options { 56 | m[opt.Key] = opt.Value 57 | } 58 | return m 59 | } 60 | 61 | /* 62 | New returns a pointer to a Client and initializes the rpc configuration with the host's Tezos netowrk constants. 63 | */ 64 | func New(host string) (*Client, error) { 65 | c := &Client{ 66 | client: resty.New(), 67 | host: cleanseHost(host), 68 | chain: "main", 69 | } 70 | 71 | _, constants, err := c.Constants(ConstantsInput{BlockID: &BlockIDHead{}}) 72 | if err != nil { 73 | return c, errors.Wrap(err, "failed to initialize library with network constants") 74 | } 75 | c.networkConstants = &constants 76 | 77 | return c, nil 78 | } 79 | 80 | // SetChain sets the chain for the rpc 81 | func (c *Client) SetChain(chain string) { 82 | c.chain = chain 83 | } 84 | 85 | // CurrentContstants returns the constants used on the client 86 | func (c *Client) CurrentContstants() Constants { 87 | return *c.networkConstants 88 | } 89 | 90 | /* 91 | OverrideClient overrides underlying network client. 92 | Can allow you to create middleware as needed: https://github.com/go-resty/resty#request-and-response-middleware 93 | */ 94 | func (c *Client) OverrideClient(client *resty.Client) { 95 | c.client = client 96 | } 97 | 98 | /* 99 | SetConstants overrides GoTezos's networkConstants. 100 | */ 101 | func (c *Client) SetConstants(constants Constants) { 102 | c.networkConstants = &constants 103 | } 104 | 105 | func (c *Client) post(path string, body interface{}, opts ...rpcOptions) (*resty.Response, error) { 106 | resp, err := c.client.R(). 107 | SetQueryParams(queryParams(opts...)). 108 | SetHeader("Content-Type", "application/json"). 109 | SetBody(body). 110 | Post(fmt.Sprintf("%s%s", c.host, path)) 111 | 112 | if err != nil { 113 | return resp, err 114 | } 115 | 116 | err = handleRPCError(resp.Body()) 117 | 118 | return resp, err 119 | } 120 | 121 | func (c *Client) get(path string, opts ...rpcOptions) (*resty.Response, error) { 122 | resp, err := c.client.R().SetQueryParams(queryParams(opts...)).Get(fmt.Sprintf("%s%s", c.host, path)) 123 | if err != nil { 124 | return resp, err 125 | } 126 | err = handleRPCError(resp.Body()) 127 | 128 | return resp, err 129 | } 130 | 131 | func handleRPCError(resp []byte) error { 132 | if regRPCError.Match(resp) { 133 | rpcErrors := Errors{} 134 | err := json.Unmarshal(resp, &rpcErrors) 135 | if err != nil { 136 | return nil 137 | } 138 | return &rpcErrors[0] 139 | } 140 | 141 | return nil 142 | } 143 | 144 | func cleanseHost(host string) string { 145 | if len(host) == 0 { 146 | return "" 147 | } 148 | if host[len(host)-1] == '/' { 149 | host = host[:len(host)-1] 150 | } 151 | if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") { 152 | host = fmt.Sprintf("http://%s", host) //default to http 153 | } 154 | return host 155 | } 156 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/delegate.json: -------------------------------------------------------------------------------- 1 | {"balance":"179736100278","frozen_balance":"136798595140","frozen_balance_by_cycle":[{"cycle":201,"deposit":"26624000000","fees":"364961","rewards":"816200000"},{"cycle":202,"deposit":"27328000000","fees":"313305","rewards":"848800000"},{"cycle":203,"deposit":"22272000000","fees":"193579","rewards":"681000000"},{"cycle":204,"deposit":"24704000000","fees":"353816","rewards":"742600000"},{"cycle":205,"deposit":"29504000000","fees":"169479","rewards":"900600000"},{"cycle":206,"deposit":"2304000000","fees":"0","rewards":"72000000"}],"staking_balance":"1564706591745","delegated_contracts":["tz1gxmCTN8BSwuPLghDydtDKTqnAKyD8QTv7","KT1JoAP7MfiigepR332u6xJqza9CG52ycYZ9","KT1JsHBFpoGRVXpcfC763YwvonKtNvaFotpG","tz1Tjpy1ibFhioZ3Y1R6N9zoW4EL54AFYph1","KT1Lm4ZSyXSHod7U6znR7z9SGVmexntNQwAp","tz1Vcu87ZuUK2e8BcoCBUWUhu2s2hPAabStm","KT1RbwPHzDwU9oPjnTWZrbCrMGjaFyj8dEtC","KT1MJZWHKZU7ViybRLsphP3ppiiTc7myP2aj","KT1MSFeAGaWk8w7F1gmgUMaarU7mH385ueYC","tz1Qadi21BxpHAjtfSrF6p4t3qMC5K8Ucjsw","KT1EbMbqTUS8XnqGVRsdLZVKLhcT7Zc33jR1","tz1VESLfEAEwDEKhyLZJYXVoervFk5ABPUUD","KT1FPyY6mAhnzyVGP8ApGvuRyF7SKcT9TDWy","KT1LfoE9EbpczdzUzowRckGUfikGcd5PyVKg","tz1RomjUZ1j9F2vqE24h2Am8UeGUpcrf6vvJ","tz1PB27kbPL64MWYoNZAfQAEmzCZFi9EvgBw","KT1NGd6RaRtmvwexYXGibtdvKBnNjjpBNknn","KT1KJ5Qt18yU9DrqN36tgyLtaSvFSZ5r6YL6","tz1LRir5SfRcC4LNfagetqzKRMRjGNBTiHNH","KT19Q8GiYqGpuuUjf9xfXXVu1WY889N8oxRe","KT1QB9UAT1okYfcPQLi4jBmZkYg7LHcepERV","KT1PY2MMiTUkZQv7CPekXy186N1qmu7GikcT","KT1NmVtU3CNqzhNWwLhE5BqAopjkcmHpWzT2","KT19ABG9KxbEz2GrdN6uhGfxLmMY7REikBN8","KT1NxnFWHW7bUxzks1oHVU2jn4heu48KC3eD","KT1GBWviYFdRiNkhwM7LfrKDgHWnpdxpURtx","KT1WQWXvRcMjJB1y6mYZytoS5QsFJyFNDCk5","KT1LgkGigaMrnim3TonQWfwDHnM3fHkF1jMv","KT1JJcydTkinquNqh6kE5HYgFpD2124qHbZp","KT1BjtEUxd25wwdwGH432LoP6PskvUc2bEYV","tz1dfUssfLfTBoYqsWxMu86ycmLUvfF2abng","KT18ni9Yar4UzwZozFbRF7SFUKg2EqyyUPPT","KT1Wp4tXL6GUtABkikB68fT7SaPQY2UuFkuE","KT1VUbpty8fER7npuvsfYDZXf2wVPhAHVqSx","KT1NfMCxyzwev243rKk3Y6SN8GfmdLKwASFQ","KT1CeUNtCrXFNbLmvdGPNnxpcJw2sW5Hcpmc","KT1AThmRzcn51NwMf25NFYTqawjVo62hWiCv","KT1REp3D8dkiVVi37TCSMJNgGeX6UigBtfaL","KT18uqwoNyPRHpHCrg7xBFd7CiAZMbS1Ffne","KT1W3oiS6s9NgSxhZY1nCsazW2QbwkmjkET1","KT1T3dPMBm7D3kKqALKYnW2mViFqMMVCYtmo","KT1RuTPgQ6kdpnE3Adnw7Hr2KFN45uC3BdBy","KT1Aeg9D8kvkbAb6yikUdFcroReXvHtMBaZz","tz1Ykmc29JfQvWnjWRPYTPUZBLW4gwa9YKUD","KT1Na4maJ99GE6CGA1vEocWXrKRmxmsVUaTi","KT1HccFB3cn4BR2za9XMuU7Wht64omed2UW8","KT1XiGwpmguFEnZDtBDDGisGxXw6qKJHPjdB","KT1XrBAocuiE3C2vvtgt7PFoazrC1KRi9ZF4","KT1S9VbEnU8nj33ufxrGBYGxBCnqmeoAnKt4","KT1PpVsfyVhWYTpyUaYigdmq1Aiv7zArTFYp","KT1PDBuQmFLVHfiWZjV248QdTrdcmAuSS7Tx","tz1hbXhPVUX1fC8hN7fALyaUpdoC6EMgqM2h","KT1A1sZmBQS9oZnPePRwP3Jyzv41xEppxfbF","KT193c72q6eP1VpaY7hiheE7k1eDZiXeQUUw","KT1KeNNxEM4NyfrmF1CG6TLn3nRSmEGhP7Z2","tz1f7mbrPU2cMHhjqhYzw9SfmZYKUtZkG52A","KT1Dgma8bbDtAbtMbYYS5VmziyCANAZn8M7W","KT1Jw925NVi4FzTVohZk5iLqagnhJGDEQoTS","tz1W3HW533csCBLor4NPtU79R2TT2sbKfJDH","KT1CySPLDUSYyJ9vqNCF2dGgit4Rw2yUNEcj","KT18kTf8UujihcF46Zn3rsFdEYFL1ZNFnGY4","KT1Lnh39om2iqr4qb9AarF9T38ayNBLnfAVn","KT1CQiyDJ3mMVDoEqLY8Fz1onFXo5ycp5BDN","KT1VzTs5piA7kYQkkfA9QNApVqGq1h6eMuV4","KT1QLo7DzPZnYK2EhmWpejVUnFjQUuWFKHnc","KT1Cz1jPLuaPR99XamKQDr9PKZY1PTXzTAHH","KT1C28u6DWsBfXk3UMyGrd8zTUVMpsyvjxmp","KT1TDrRrdz6SLYLBw8ZDxLWwJpx7FVpC52bt","KT1XPMJx2wuCbbzKZx5jJyKqLpPJMHv58wni","KT1J2uk1fYSnZjxkJcUhFDkaRDhjCTRBspqv","tz1Lv6nFvAWMvNRbQF7UcX4jobGLrAhKQLNN","tz1R9vogbJQ4QpEnhFjut6SfyoopP17KkdMc","KT1TS49jiXxrnwhoJzAvCzGZCXLJs3XV1k6C","KT1MX2TwjSBzPaSsBUeW2k9DKehpiuMGfFcL","KT1A5seo53aLSSyHgJKZFYnh7jTZBtFnNnjz","tz1aX2DF3ioDjqDcTVmrxVuqkxhZh1pLtfHU","KT1LinsZAnyxajEv4eNFWtwHMdyhbJsGfvp3","tz1MXhttCeSJYpF3QRmPkMLCfNZaVufEuJmJ","tz1SnvfwMUYfD2uJrHBiaj4XPstW3eUE9RJU","tz1Lfs9xYtCvj1xe5UCPG8Gv78d3mFAJn4Dx","tz1Zuav4ZBoiYhn4btW4HSr7G7J4txGZjvbu","KT1Re5utTU2hrujXgZ3Ux5BgjN8rbru4sns2","tz1Wq6LVwpofZ6zqjMBuLyEU53hRMepqkXEr","KT1J4WFQRV3942phzRrh87WDFWKrNVcDJTP9","KT1JPeGNVarLsPZnSb3hG5xMVmJJmmBnrnpT","KT1AT7N9bGhViSorUrpivuYT6Wxs37hR2p9d","KT1BXmBgMSViAViNyhvkb441e2RBFMiKdnj7","KT1E1MnvNgCDLqnGneStVY9CvmjnyZgcaPaD","KT1EidADxWfYeBgK8L1ZTbf7a9zyjKwCFjfH","KT19Aro5JcjKH7J7RA6sCRihPiBQzQED3oQC","KT1C8S2vLYbzgQHhdC8MBehunhcp1Q9hj6MC","tz1Z48RMPT1vjqNyUASCexnCEvEEE93J1pwL","tz1hZZn4rsHLXdgQ9d8Rne9CLo6VFo29uQ3m","KT1MfT8XvQp9ZeGUx4cmCNF3wui55WLNYhq9","tz1VeiAS5wvYgNdri6vwDUrctQ5XhhaXY3K9","KT1K4xei3yozp7UP5rHV5wuoDzWwBXqCGRBt","KT1GcSsQaTtMB2HvUKU9b6WRFUnGpGx9JwGk","KT1WBDsJhoRvsvsRCmJirz9AFhSySSvzTWVd","KT1Uh1G9tdq45N63ZBrreDKy7eZF8QVoydm1","KT1UVUasDXH6mg8NCzRRgqvcjMoDUpETYEzH"],"delegated_balance":"1389031691467","deactivated":false,"grace_period":212} -------------------------------------------------------------------------------- /keys/key.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/sha512" 6 | "encoding/base64" 7 | "encoding/hex" 8 | "fmt" 9 | 10 | tzcrypt "github.com/goat-systems/go-tezos/v4/internal/crypto" 11 | "github.com/pkg/errors" 12 | "github.com/tyler-smith/go-bip39" 13 | "golang.org/x/crypto/nacl/secretbox" 14 | "golang.org/x/crypto/pbkdf2" 15 | ) 16 | 17 | // Key is the cryptographic key to a Tezos Wallet 18 | type Key struct { 19 | curve iCurve 20 | privKey []byte 21 | PubKey PubKey 22 | } 23 | 24 | /* 25 | Generate returns a new cryptographic key based on the kind of elliptical curve passed 26 | * Ed25519 27 | * Secp256k1 28 | * NistP256 29 | */ 30 | func Generate(kind ECKind) (*Key, error) { 31 | token := make([]byte, 32) 32 | rand.Read(token) 33 | return key(token, kind) 34 | } 35 | 36 | // FromBytes returns a new key from a private key in byte form 37 | func FromBytes(privKey []byte, kind ECKind) (*Key, error) { 38 | return key(privKey, kind) 39 | } 40 | 41 | // FromHex returns a new key from a private key in hex form 42 | func FromHex(privKey string, kind ECKind) (*Key, error) { 43 | v, err := hex.DecodeString(privKey) 44 | if err != nil { 45 | return nil, errors.Wrap(err, "failed to import key") 46 | } 47 | 48 | return key(v, kind) 49 | } 50 | 51 | // FromBase64 returns a new key from a private key in base64 form 52 | func FromBase64(privKey string, kind ECKind) (*Key, error) { 53 | v, err := base64.StdEncoding.DecodeString(privKey) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "failed to import key") 56 | } 57 | return key(v, kind) 58 | } 59 | 60 | // FromBase58 returns a new key from a private key in base58 form 61 | func FromBase58(privKey string, kind ECKind) (*Key, error) { 62 | if len(privKey) < 4 { 63 | return nil, errors.New("failed to import key: invalid key length") 64 | } 65 | 66 | curve, err := getCurveByPrefix(privKey[0:4]) 67 | if err != nil { 68 | return nil, errors.Wrap(err, "failed to import key") 69 | } 70 | 71 | return key(tzcrypt.B58cdecode(privKey, curve.privateKeyPrefix()), curve.getECKind()) 72 | } 73 | 74 | // FromEncryptedSecret returns a new key from an encrypted private key 75 | func FromEncryptedSecret(esk, passwd string) (*Key, error) { 76 | curve, err := getCurveByPrefix(esk[:5]) 77 | if err != nil { 78 | return &Key{}, err 79 | } 80 | 81 | // Convert key from base58 to []byte 82 | b58c, err := tzcrypt.Decode(esk) 83 | if err != nil { 84 | return &Key{}, errors.Wrap(err, "failed to import key") 85 | } 86 | 87 | // Strip off prefix and extract parts 88 | esb := b58c[5:] 89 | salt := esb[:8] 90 | esm := esb[8:] // encrypted key 91 | 92 | // Convert string pw to []byte 93 | passWd := []byte(passwd) 94 | 95 | // Derive a key from password, salt and number of iterations 96 | pbkdf2key := pbkdf2.Key(passWd, salt, 32768, 32, sha512.New) 97 | var byteKey [32]byte 98 | for i := range pbkdf2key { 99 | byteKey[i] = pbkdf2key[i] 100 | } 101 | 102 | var out []byte 103 | var emptyNonceBytes [24]byte 104 | 105 | unencSecret, ok := secretbox.Open(out, esm, &emptyNonceBytes, &byteKey) 106 | if !ok { 107 | return &Key{}, errors.New("failed to import key: invalid password") 108 | } 109 | 110 | return key(unencSecret, curve.getECKind()) 111 | } 112 | 113 | // FromMnemonic returns a new key from a mnemonic 114 | func FromMnemonic(mnemonic, email, passwd string, kind ECKind) (*Key, error) { 115 | seed, err := bip39.NewSeedWithErrorChecking(mnemonic, fmt.Sprintf("%s%s", email, passwd)) 116 | if err != nil { 117 | return &Key{}, err 118 | } 119 | 120 | return key(seed, kind) 121 | } 122 | 123 | func key(v []byte, kind ECKind) (*Key, error) { 124 | curve := getCurve(kind) 125 | pubKey, err := newPubKey(curve.getPrivateKey(v), kind) 126 | if err != nil { 127 | return nil, errors.Wrap(err, "failed to import key") 128 | } 129 | 130 | return &Key{ 131 | curve: curve, 132 | privKey: curve.getPrivateKey(v), 133 | PubKey: pubKey, 134 | }, nil 135 | } 136 | 137 | // GetBytes will return the raw bytes of the private key. 138 | func (k *Key) GetBytes() []byte { 139 | return k.privKey 140 | } 141 | 142 | /* 143 | GetSecretKey will return the base58 encoded key with the secret key prefix. This is unencrypted. 144 | Example: edskRpfRbhVr7SjmVpMK1kzTDrSzuCKroxjQAsfJn94X7LgbpqJLvRDHfNHFT9KbCZAXVVhMmkQGz4APscezMbJFov5ZNPSY9H 145 | */ 146 | func (k *Key) GetSecretKey() string { 147 | return tzcrypt.B58cencode(k.privKey, k.curve.privateKeyPrefix()) 148 | } 149 | 150 | // SignHex will sign a hex encoded string 151 | func (k *Key) SignHex(msg string) (Signature, error) { 152 | bytes, err := hex.DecodeString(msg) 153 | if err != nil { 154 | return Signature{}, errors.Wrap(err, "failed to hex decode message") 155 | } 156 | 157 | return k.curve.sign(checkAndAddWaterMark(bytes), k.privKey) 158 | } 159 | 160 | // SignBytes will sign a byte message 161 | func (k *Key) SignBytes(msg []byte) (Signature, error) { 162 | return k.curve.sign(checkAndAddWaterMark(msg), k.privKey) 163 | } 164 | 165 | func checkAndAddWaterMark(v []byte) []byte { 166 | if v != nil { 167 | if v[0] != byte(3) { 168 | v = append([]byte{3}, v...) 169 | } 170 | } 171 | 172 | return v 173 | } 174 | -------------------------------------------------------------------------------- /rpc/block_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package rpc_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/goat-systems/go-tezos/v4/rpc" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_Integration_Block(t *testing.T) { 13 | r, err := rpc.New(HOST) 14 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 15 | t.FailNow() 16 | } 17 | 18 | for i := 0; i < 20; i++ { 19 | getRandomBlock(r, 0, t) 20 | } 21 | } 22 | 23 | // TODO: func Test_Integration_EndorsingPower(t *testing.T) {} 24 | 25 | func Test_Integration_Hash(t *testing.T) { 26 | r, err := rpc.New(HOST) 27 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 28 | t.FailNow() 29 | } 30 | 31 | if _, _, err := r.Hash(&rpc.BlockIDHead{}); err != nil { 32 | t.FailNow() 33 | } 34 | } 35 | 36 | func Test_Integration_Header(t *testing.T) { 37 | r, err := rpc.New(HOST) 38 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 39 | t.FailNow() 40 | } 41 | 42 | if _, _, err := r.Header(&rpc.BlockIDHead{}); err != nil { 43 | t.FailNow() 44 | } 45 | } 46 | 47 | func Test_Integration_HeaderRaw(t *testing.T) { 48 | r, err := rpc.New(HOST) 49 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 50 | t.FailNow() 51 | } 52 | 53 | if _, _, err := r.HeaderRaw(&rpc.BlockIDHead{}); err != nil { 54 | t.FailNow() 55 | } 56 | } 57 | 58 | func Test_Integration_HeaderShell(t *testing.T) { 59 | r, err := rpc.New(HOST) 60 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 61 | t.FailNow() 62 | } 63 | 64 | if _, _, err := r.HeaderShell(&rpc.BlockIDHead{}); err != nil { 65 | t.FailNow() 66 | } 67 | } 68 | 69 | func Test_Integration_HeaderProtocolData(t *testing.T) { 70 | r, err := rpc.New(HOST) 71 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 72 | t.FailNow() 73 | } 74 | 75 | if _, _, err := r.HeaderProtocolData(&rpc.BlockIDHead{}); err != nil { 76 | t.FailNow() 77 | } 78 | } 79 | 80 | func Test_Integration_HeaderProtocolDataRaw(t *testing.T) { 81 | r, err := rpc.New(HOST) 82 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 83 | t.FailNow() 84 | } 85 | 86 | if _, _, err := r.HeaderProtocolDataRaw(&rpc.BlockIDHead{}); err != nil { 87 | t.FailNow() 88 | } 89 | } 90 | 91 | func Test_Integration_LiveBlocks(t *testing.T) { 92 | r, err := rpc.New(HOST) 93 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 94 | t.FailNow() 95 | } 96 | 97 | if _, _, err := r.LiveBlocks(&rpc.BlockIDHead{}); err != nil { 98 | t.FailNow() 99 | } 100 | } 101 | 102 | func Test_Integration_Metadata(t *testing.T) { 103 | r, err := rpc.New(HOST) 104 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 105 | t.FailNow() 106 | } 107 | 108 | if _, _, err := r.Metadata(&rpc.BlockIDHead{}); err != nil { 109 | t.FailNow() 110 | } 111 | } 112 | 113 | func Test_Integration_MinimalValidTime(t *testing.T) { 114 | r, err := rpc.New(HOST) 115 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 116 | t.FailNow() 117 | } 118 | 119 | if _, _, err := r.MinimalValidTime(rpc.MinimalValidTimeInput{ 120 | BlockID: &rpc.BlockIDHead{}, 121 | }); err != nil { 122 | t.FailNow() 123 | } 124 | } 125 | 126 | func Test_Integration_OperationHashes(t *testing.T) { 127 | r, err := rpc.New(HOST) 128 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 129 | t.FailNow() 130 | } 131 | 132 | if _, _, err := r.OperationHashes(rpc.OperationHashesInput{ 133 | BlockID: &rpc.BlockIDHead{}, 134 | }); err != nil { 135 | t.FailNow() 136 | } 137 | 138 | if _, _, err := r.OperationHashes(rpc.OperationHashesInput{ 139 | BlockID: &rpc.BlockIDHead{}, 140 | ListOffset: "0", 141 | }); err != nil { 142 | t.FailNow() 143 | } 144 | 145 | if _, _, err := r.OperationHashes(rpc.OperationHashesInput{ 146 | BlockID: &rpc.BlockIDHead{}, 147 | ListOffset: "0", 148 | OperationOffset: "1", 149 | }); err != nil { 150 | t.FailNow() 151 | } 152 | } 153 | 154 | func Test_Integration_Operations(t *testing.T) { 155 | r, err := rpc.New(HOST) 156 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 157 | t.FailNow() 158 | } 159 | 160 | if _, _, err := r.Operations(rpc.OperationsInput{ 161 | BlockID: &rpc.BlockIDHead{}, 162 | }); err != nil { 163 | t.FailNow() 164 | } 165 | 166 | if _, _, err := r.Operations(rpc.OperationsInput{ 167 | BlockID: &rpc.BlockIDHead{}, 168 | ListOffset: "0", 169 | }); err != nil { 170 | t.FailNow() 171 | } 172 | 173 | if _, _, err := r.Operations(rpc.OperationsInput{ 174 | BlockID: &rpc.BlockIDHead{}, 175 | ListOffset: "0", 176 | OperationOffset: "1", 177 | }); err != nil { 178 | t.FailNow() 179 | } 180 | } 181 | 182 | func Test_Integration_Protocols(t *testing.T) { 183 | r, err := rpc.New(HOST) 184 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 185 | t.FailNow() 186 | } 187 | 188 | if _, _, err := r.Protocols(&rpc.BlockIDHead{}); err != nil { 189 | t.FailNow() 190 | } 191 | } 192 | 193 | func Test_Integration_RequiredEndorsements(t *testing.T) { 194 | r, err := rpc.New(HOST) 195 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 196 | t.FailNow() 197 | } 198 | 199 | if _, _, err := r.RequiredEndorsements(rpc.RequiredEndorsementsInput{ 200 | BlockID: &rpc.BlockIDHead{}, 201 | }); err != nil { 202 | t.FailNow() 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /rpc/iface.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/go-resty/resty/v2" 8 | ) 9 | 10 | // IFace is an interface mocking a GoTezos object. 11 | type IFace interface { 12 | Block(blockID BlockID) (*resty.Response, *Block, error) 13 | EndorsingPower(input EndorsingPowerInput) (*resty.Response, int, error) 14 | Hash(blockID BlockID) (*resty.Response, string, error) 15 | Header(blockID BlockID) (*resty.Response, Header, error) 16 | HeaderRaw(blockID BlockID) (*resty.Response, string, error) 17 | HeaderShell(blockID BlockID) (*resty.Response, HeaderShell, error) 18 | HeaderProtocolData(blockID BlockID) (*resty.Response, ProtocolData, error) 19 | HeaderProtocolDataRaw(blockID BlockID) (*resty.Response, string, error) 20 | LiveBlocks(blockID BlockID) (*resty.Response, []string, error) 21 | Metadata(blockID BlockID) (*resty.Response, Metadata, error) 22 | MetadataHash(blockID BlockID) (*resty.Response, string, error) 23 | MinimalValidTime(input MinimalValidTimeInput) (*resty.Response, time.Time, error) 24 | OperationHashes(input OperationHashesInput) (*resty.Response, OperationHashes, error) 25 | OperationMetadataHashes(input OperationMetadataHashesInput) (*resty.Response, OperationMetadataHashes, error) 26 | Operations(input OperationsInput) (*resty.Response, FlattenedOperations, error) 27 | OperationsMetadataHash(blockID BlockID) (*resty.Response, string, error) 28 | Protocols(blockID BlockID) (*resty.Response, Protocols, error) 29 | RequiredEndorsements(input RequiredEndorsementsInput) (*resty.Response, int, error) 30 | BigMap(input BigMapInput) (*resty.Response, error) 31 | Constants(input ConstantsInput) (*resty.Response, Constants, error) 32 | Contracts(input ContractsInput) (*resty.Response, []string, error) 33 | Contract(input ContractInput) (*resty.Response, Contract, error) 34 | ContractBalance(input ContractBalanceInput) (*resty.Response, string, error) 35 | ContractCounter(input ContractCounterInput) (*resty.Response, int, error) 36 | ContractDelegate(input ContractDelegateInput) (*resty.Response, string, error) 37 | ContractEntrypoints(input ContractEntrypointsInput) (*resty.Response, map[string]*json.RawMessage, error) 38 | ContractEntrypoint(input ContractEntrypointInput) (*resty.Response, *json.RawMessage, error) 39 | ContractManagerKey(input ContractManagerKeyInput) (*resty.Response, string, error) 40 | ContractScript(input ContractScriptInput) (*resty.Response, error) 41 | ContractSaplingDiff(input ContractSaplingDiffInput) (*resty.Response, error) 42 | ContractStorage(input ContractStorageInput) (*resty.Response, error) 43 | Delegates(input DelegatesInput) (*resty.Response, []string, error) 44 | Delegate(input DelegateInput) (*resty.Response, Delegate, error) 45 | DelegateBalance(input DelegateBalanceInput) (*resty.Response, string, error) 46 | DelegateDeactivated(input DelegateDeactivatedInput) (*resty.Response, bool, error) 47 | DelegateDelegatedBalance(input DelegateDelegatedBalanceInput) (*resty.Response, string, error) 48 | DelegateDelegatedContracts(input DelegateDelegatedContractsInput) (*resty.Response, []string, error) 49 | DelegateFrozenBalance(input DelegateFrozenBalanceInput) (*resty.Response, string, error) 50 | DelegateFrozenBalanceByCycle(input DelegateFrozenBalanceByCycleInput) (*resty.Response, []FrozenBalanceByCycle, error) 51 | DelegateGracePeriod(input DelegateGracePeriodInput) (*resty.Response, int, error) 52 | DelegateStakingBalance(input DelegateStakingBalanceInput) (*resty.Response, string, error) 53 | DelegateVotingPower(input DelegateVotingPowerInput) (*resty.Response, int, error) 54 | Nonces(input NoncesInput) (*resty.Response, Nonces, error) 55 | RawBytes(input RawBytesInput) (*resty.Response, error) 56 | SaplingDiff(input SaplingDiffInput) (*resty.Response, error) 57 | Seed(input SeedInput) (*resty.Response, string, error) 58 | Cycle(cycle int) (*resty.Response, Cycle, error) 59 | BakingRights(input BakingRightsInput) (*resty.Response, []BakingRights, error) 60 | CompletePrefix(input CompletePrefixInput) (*resty.Response, []string, error) 61 | CurrentLevel(input CurrentLevelInput) (*resty.Response, CurrentLevel, error) 62 | EndorsingRights(input EndorsingRightsInput) (*resty.Response, []EndorsingRights, error) 63 | ForgeOperations(input ForgeOperationsInput) (*resty.Response, string, error) 64 | ForgeBlockHeader(input ForgeBlockHeaderInput) (*resty.Response, ForgeBlockHeader, error) 65 | LevelsInCurrentCycle(input LevelsInCurrentCycleInput) (*resty.Response, LevelsInCurrentCycle, error) 66 | ParseBlock(input ParseBlockInput) (*resty.Response, BlockHeaderSignedContents, error) 67 | ParseOperations(input ParseOperationsInput) (*resty.Response, []Operations, error) 68 | PreapplyBlock(input PreapplyBlockInput) (*resty.Response, PreappliedBlock, error) 69 | PreapplyOperations(input PreapplyOperationsInput) (*resty.Response, []Operations, error) 70 | Entrypoint(input EntrypointInput) (*resty.Response, Entrypoint, error) 71 | Entrypoints(input EntrypointsInput) (*resty.Response, Entrypoints, error) 72 | PackData(input PackDataInput) (*resty.Response, PackedData, error) 73 | RunCode(input RunCodeInput) (*resty.Response, RanCode, error) 74 | RunOperation(input RunOperationInput) (*resty.Response, Operations, error) 75 | TraceCode(input TraceCodeInput) (*resty.Response, TracedCode, error) 76 | TypecheckCode(input TypeCheckcodeInput) (*resty.Response, TypecheckedCode, error) 77 | TypecheckData(input TypecheckDataInput) (*resty.Response, TypecheckedData, error) 78 | BallotList(blockID BlockID) (*resty.Response, BallotList, error) 79 | Ballots(blockID BlockID) (*resty.Response, Ballots, error) 80 | CurrentPeriod(blockID BlockID) (*resty.Response, VotingPeriod, error) 81 | CurrentPeriodKind(blockID BlockID) (*resty.Response, string, error) 82 | CurrentProposal(blockID BlockID) (*resty.Response, string, error) 83 | CurrentQuorum(blockID BlockID) (*resty.Response, int, error) 84 | Listings(blockID BlockID) (*resty.Response, Listings, error) 85 | Proposals(blockID BlockID) (*resty.Response, Proposals, error) 86 | SuccessorPeriod(blockID BlockID) (*resty.Response, VotingPeriod, error) 87 | TotalVotingPower(blockID BlockID) (*resty.Response, int, error) 88 | GetFA12Balance(input GetFA12BalanceInput) (*resty.Response, string, error) 89 | GetFA12Supply(input GetFA12SupplyInput) (*resty.Response, string, error) 90 | GetFA12Allowance(input GetFA12AllowanceInput) (*resty.Response, string, error) 91 | InjectionOperation(input InjectionOperationInput) (*resty.Response, string, error) 92 | InjectionBlock(input InjectionBlockInput) (*resty.Response, error) 93 | Connections() (*resty.Response, Connections, error) 94 | ActiveChains() (*resty.Response, ActiveChains, error) 95 | } 96 | -------------------------------------------------------------------------------- /rpc/independent_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/goat-systems/go-tezos/v4/rpc" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func Test_InjectOperation(t *testing.T) { 14 | goldenOp := "a732d3520eeaa3de98d78e5e5cb6c85f72204fd46feb9f76853841d4a701add36c0008ba0cb2fad622697145cf1665124096d25bc31ef44e0af44e00b960000008ba0cb2fad622697145cf1665124096d25bc31e006c0008ba0cb2fad622697145cf1665124096d25bc31ed3e7bd1008d3bb0300b1a803000008ba0cb2fad622697145cf1665124096d25bc31e00" 15 | goldenHash := []byte(`"oopfasdfadjkfalksj"`) 16 | 17 | type want struct { 18 | err bool 19 | errContains string 20 | result string 21 | } 22 | 23 | cases := []struct { 24 | name string 25 | input http.Handler 26 | want want 27 | }{ 28 | { 29 | "handles RPC error", 30 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regInjectionOperation, readResponse(rpcerrors)}, blankHandler)), 31 | want{ 32 | true, 33 | "failed to inject operation", 34 | "", 35 | }, 36 | }, 37 | { 38 | "handles failure to unmarshal", 39 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regInjectionOperation, []byte(`junk`)}, blankHandler)), 40 | want{ 41 | true, 42 | "failed to inject operation: failed to parse json", 43 | "", 44 | }, 45 | }, 46 | { 47 | "is successful", 48 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regInjectionOperation, goldenHash}, blankHandler)), 49 | want{ 50 | false, 51 | "", 52 | "oopfasdfadjkfalksj", 53 | }, 54 | }, 55 | } 56 | 57 | for _, tt := range cases { 58 | t.Run(tt.name, func(t *testing.T) { 59 | server := httptest.NewServer(tt.input) 60 | defer server.Close() 61 | 62 | r, err := rpc.New(server.URL) 63 | assert.Nil(t, err) 64 | 65 | _, result, err := r.InjectionOperation(rpc.InjectionOperationInput{ 66 | Operation: goldenOp, 67 | }) 68 | checkErr(t, tt.want.err, tt.want.errContains, err) 69 | assert.Equal(t, tt.want.result, result) 70 | }) 71 | } 72 | } 73 | 74 | func Test_InjectBlock(t *testing.T) { 75 | goldenRPCError := readResponse(rpcerrors) 76 | goldenHash := []byte("some_hash") 77 | 78 | type want struct { 79 | err bool 80 | errContains string 81 | result []byte 82 | } 83 | 84 | cases := []struct { 85 | name string 86 | input http.Handler 87 | want want 88 | }{ 89 | { 90 | "handles RPC error", 91 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regInjectionBlock, readResponse(rpcerrors)}, blankHandler)), 92 | want{ 93 | true, 94 | "failed to inject block", 95 | goldenRPCError, 96 | }, 97 | }, 98 | { 99 | "is successful", 100 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regInjectionBlock, goldenHash}, blankHandler)), 101 | want{ 102 | false, 103 | "", 104 | goldenHash, 105 | }, 106 | }, 107 | } 108 | 109 | for _, tt := range cases { 110 | t.Run(tt.name, func(t *testing.T) { 111 | server := httptest.NewServer(tt.input) 112 | defer server.Close() 113 | 114 | r, err := rpc.New(server.URL) 115 | assert.Nil(t, err) 116 | 117 | result, err := r.InjectionBlock(rpc.InjectionBlockInput{ 118 | Block: &rpc.Block{}, 119 | }) 120 | checkErr(t, tt.want.err, tt.want.errContains, err) 121 | assert.Equal(t, tt.want.result, result.Body()) 122 | }) 123 | } 124 | } 125 | 126 | func Test_Connections(t *testing.T) { 127 | var goldenConnections rpc.Connections 128 | json.Unmarshal(readResponse(connections), &goldenConnections) 129 | 130 | type want struct { 131 | wantErr bool 132 | containsErr string 133 | wantConnections rpc.Connections 134 | } 135 | 136 | cases := []struct { 137 | name string 138 | inputHanler http.Handler 139 | want 140 | }{ 141 | { 142 | "handles RPC error", 143 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regConnections, readResponse(rpcerrors)}, blankHandler)), 144 | want{ 145 | true, 146 | "failed to get network connections", 147 | rpc.Connections{}, 148 | }, 149 | }, 150 | { 151 | "handles failure to unmarshal", 152 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regConnections, []byte(`junk`)}, blankHandler)), 153 | want{ 154 | true, 155 | "failed to get network connections: failed to parse json", 156 | rpc.Connections{}, 157 | }, 158 | }, 159 | { 160 | "is successful", 161 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regConnections, readResponse(connections)}, blankHandler)), 162 | want{ 163 | false, 164 | "", 165 | goldenConnections, 166 | }, 167 | }, 168 | } 169 | 170 | for _, tt := range cases { 171 | t.Run(tt.name, func(t *testing.T) { 172 | server := httptest.NewServer(tt.inputHanler) 173 | defer server.Close() 174 | 175 | rpc, err := rpc.New(server.URL) 176 | assert.Nil(t, err) 177 | 178 | _, connections, err := rpc.Connections() 179 | checkErr(t, tt.wantErr, tt.containsErr, err) 180 | assert.Equal(t, tt.want.wantConnections, connections) 181 | }) 182 | } 183 | } 184 | 185 | func Test_ActiveChains(t *testing.T) { 186 | type want struct { 187 | err bool 188 | errContains string 189 | activeChains rpc.ActiveChains 190 | } 191 | 192 | cases := []struct { 193 | name string 194 | input http.Handler 195 | want want 196 | }{ 197 | { 198 | "handles RPC error", 199 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regActiveChains, readResponse(rpcerrors)}, blankHandler)), 200 | want{ 201 | true, 202 | "failed to get active chains", 203 | rpc.ActiveChains{}, 204 | }, 205 | }, 206 | { 207 | "handles failure to unmarshal", 208 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regActiveChains, []byte(`junk`)}, blankHandler)), 209 | want{ 210 | true, 211 | "failed to get active chains: failed to parse json", 212 | rpc.ActiveChains{}, 213 | }, 214 | }, 215 | { 216 | "is successful", 217 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regActiveChains, readResponse(activechains)}, blankHandler)), 218 | want{ 219 | false, 220 | "", 221 | rpc.ActiveChains{ 222 | { 223 | ChainID: "NetXdQprcVkpaWU", 224 | }, 225 | }, 226 | }, 227 | }, 228 | } 229 | 230 | for _, tt := range cases { 231 | t.Run(tt.name, func(t *testing.T) { 232 | server := httptest.NewServer(tt.input) 233 | defer server.Close() 234 | 235 | rpc, err := rpc.New(server.URL) 236 | assert.Nil(t, err) 237 | 238 | _, activeChains, err := rpc.ActiveChains() 239 | checkErr(t, tt.want.err, tt.want.errContains, err) 240 | assert.Equal(t, tt.want.activeChains, activeChains) 241 | }) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /rpc/independent.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | validator "github.com/go-playground/validator/v10" 8 | "github.com/go-resty/resty/v2" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | /* 13 | InjectionOperationInput is the input for the InjectionOperation function. 14 | 15 | RPC: 16 | https://tezos.gitlab.io/shell/rpc.html#post-injection-operation 17 | */ 18 | type InjectionOperationInput struct { 19 | // The operation string. 20 | Operation string `validate:"required"` 21 | 22 | // If ?async is true, the function returns immediately. 23 | Async bool 24 | 25 | // Specify the ChainID. 26 | ChainID string 27 | } 28 | 29 | func (i *InjectionOperationInput) contructRPCOptions() []rpcOptions { 30 | var opts []rpcOptions 31 | if i.Async { 32 | opts = append(opts, rpcOptions{ 33 | "async", 34 | "true", 35 | }) 36 | } 37 | 38 | if i.ChainID != "" { 39 | opts = append(opts, rpcOptions{ 40 | "chain_id", 41 | i.ChainID, 42 | }) 43 | } 44 | return opts 45 | } 46 | 47 | /* 48 | InjectionOperation injects an operation in node and broadcast it. Returns the ID of the operation. 49 | The `signedOperationContents` should be constructed using a contextual RPCs from the latest block 50 | and signed by the client. By default, the RPC will wait for the operation to be (pre-)validated 51 | before answering. See RPCs under /blocks/prevalidation for more details on the prevalidation context. 52 | If ?async is true, the function returns immediately. Otherwise, the operation will be validated before 53 | the result is returned. An optional ?chain parameter can be used to specify whether to inject on the 54 | test chain or the main chain. 55 | 56 | Path: 57 | /injection/operation (POST) 58 | 59 | RPC: 60 | https://tezos.gitlab.io/shell/rpc.html#post-injection-operation 61 | */ 62 | func (c *Client) InjectionOperation(input InjectionOperationInput) (*resty.Response, string, error) { 63 | err := validator.New().Struct(input) 64 | if err != nil { 65 | return nil, "", errors.Wrap(err, "failed to inject operation: invalid input") 66 | } 67 | 68 | v, err := json.Marshal(input.Operation) 69 | if err != nil { 70 | return nil, "", errors.Wrap(err, "failed to inject operation") 71 | } 72 | resp, err := c.post("/injection/operation", v, input.contructRPCOptions()...) 73 | if err != nil { 74 | return resp, "", errors.Wrap(err, "failed to inject operation") 75 | } 76 | 77 | var opstring string 78 | err = json.Unmarshal(resp.Body(), &opstring) 79 | if err != nil { 80 | return resp, "", errors.Wrap(err, "failed to inject operation: failed to parse json") 81 | } 82 | 83 | return resp, opstring, nil 84 | } 85 | 86 | /* 87 | InjectionBlockInput is the input for the InjectionBlock function. 88 | 89 | RPC: 90 | https://tezos.gitlab.io/shell/rpc.html#post-injection-block 91 | */ 92 | type InjectionBlockInput struct { 93 | // Block to inject 94 | Block *Block `validate:"required"` 95 | 96 | // If ?async is true, the function returns immediately. 97 | Async bool 98 | 99 | // If ?force is true, it will be injected even on non strictly increasing fitness. 100 | Force bool 101 | 102 | // Specify the ChainID. 103 | ChainID string 104 | } 105 | 106 | func (i *InjectionBlockInput) contructRPCOptions() []rpcOptions { 107 | var opts []rpcOptions 108 | if i.Async { 109 | opts = append(opts, rpcOptions{ 110 | "async", 111 | "true", 112 | }) 113 | } 114 | 115 | if i.Force { 116 | opts = append(opts, rpcOptions{ 117 | "force", 118 | "true", 119 | }) 120 | } 121 | 122 | if i.ChainID != "" { 123 | opts = append(opts, rpcOptions{ 124 | "chain_id", 125 | i.ChainID, 126 | }) 127 | } 128 | return opts 129 | } 130 | 131 | /* 132 | InjectionBlock inject a block in the node and broadcast it. The `operations` 133 | embedded in `blockHeader` might be pre-validated using a contextual RPCs 134 | from the latest block (e.g. '/blocks/head/context/preapply'). Returns the 135 | ID of the block. By default, the RPC will wait for the block to be validated 136 | before answering. If ?async is true, the function returns immediately. Otherwise, 137 | the block will be validated before the result is returned. If ?force is true, it 138 | will be injected even on non strictly increasing fitness. An optional ?chain parameter 139 | can be used to specify whether to inject on the test chain or the main chain. 140 | 141 | Path: 142 | /injection/operation (POST) 143 | 144 | RPC: 145 | https://tezos.gitlab.io/shell/rpc.html#post-injection-block 146 | */ 147 | func (c *Client) InjectionBlock(input InjectionBlockInput) (*resty.Response, error) { 148 | err := validator.New().Struct(input) 149 | if err != nil { 150 | return nil, errors.Wrap(err, "failed to inject block: invalid input") 151 | } 152 | 153 | resp, err := c.post("/injection/block", *input.Block, input.contructRPCOptions()...) 154 | if err != nil { 155 | return resp, errors.Wrap(err, "failed to inject block") 156 | } 157 | return resp, nil 158 | } 159 | 160 | /* 161 | Connections is the network connections of a tezos node. 162 | 163 | Path: 164 | /network/connections (GET) 165 | 166 | RPC: 167 | https://tezos.gitlab.io/shell/rpc.html#get-network-connections 168 | */ 169 | type Connections []struct { 170 | Incoming bool `json:"incoming"` 171 | PeerID string `json:"peer_id"` 172 | IDPoint struct { 173 | Addr string `json:"addr"` 174 | Port int `json:"port"` 175 | } `json:"id_point"` 176 | RemoteSocketPort int `json:"remote_socket_port"` 177 | Versions []struct { 178 | Name string `json:"name"` 179 | Major int `json:"major"` 180 | Minor int `json:"minor"` 181 | } `json:"versions"` 182 | Private bool `json:"private"` 183 | LocalMetadata struct { 184 | DisableMempool bool `json:"disable_mempool"` 185 | PrivateNode bool `json:"private_node"` 186 | } `json:"local_metadata"` 187 | RemoteMetadata struct { 188 | DisableMempool bool `json:"disable_mempool"` 189 | PrivateNode bool `json:"private_node"` 190 | } `json:"remote_metadata"` 191 | } 192 | 193 | /* 194 | Connections lists the running P2P connection. 195 | 196 | Path: 197 | /network/connections (GET) 198 | 199 | RPC: 200 | https://tezos.gitlab.io/shell/rpc.html#get-network-connections 201 | */ 202 | func (c *Client) Connections() (*resty.Response, Connections, error) { 203 | resp, err := c.get("/network/connections") 204 | if err != nil { 205 | return resp, Connections{}, errors.Wrapf(err, "failed to get network connections") 206 | } 207 | 208 | var connections Connections 209 | err = json.Unmarshal(resp.Body(), &connections) 210 | if err != nil { 211 | return resp, Connections{}, errors.Wrapf(err, "failed to get network connections: failed to parse json") 212 | } 213 | 214 | return resp, connections, nil 215 | } 216 | 217 | /* 218 | ActiveChains is the active chains on the tezos network. 219 | 220 | RPC: 221 | https://tezos.gitlab.io/shell/rpc.html#get-monitor-active-chains 222 | */ 223 | type ActiveChains []struct { 224 | ChainID string `json:"chain_id"` 225 | TestProtocol string `json:"test_protocol"` 226 | ExpirationDate time.Time `json:"expiration_date"` 227 | Stopping string `json:"stopping"` 228 | } 229 | 230 | /* 231 | ActiveChains monitor every chain creation and destruction. Currently active chains will be given as first elements. 232 | 233 | Path: 234 | /monitor/active_chains (GET) 235 | 236 | RPC: 237 | https://tezos.gitlab.io/shell/rpc.html#get-monitor-active-chains 238 | */ 239 | func (c *Client) ActiveChains() (*resty.Response, ActiveChains, error) { 240 | resp, err := c.get("/monitor/active_chains") 241 | if err != nil { 242 | return nil, ActiveChains{}, errors.Wrap(err, "failed to get active chains") 243 | } 244 | 245 | var activeChains ActiveChains 246 | err = json.Unmarshal(resp.Body(), &activeChains) 247 | if err != nil { 248 | return resp, ActiveChains{}, errors.Wrap(err, "failed to get active chains: failed to parse json") 249 | } 250 | 251 | return resp, activeChains, nil 252 | } 253 | -------------------------------------------------------------------------------- /keys/key_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | 7 | tzcrypt "github.com/goat-systems/go-tezos/v4/internal/crypto" 8 | "github.com/goat-systems/go-tezos/v4/internal/testutils" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_FromEncryptedSecret(t *testing.T) { 13 | type want struct { 14 | wantErr bool 15 | containsErr string 16 | secretKey string 17 | publicKey string 18 | address string 19 | } 20 | 21 | type input struct { 22 | Esk string 23 | Password string 24 | } 25 | 26 | cases := []struct { 27 | name string 28 | input input 29 | want want 30 | }{ 31 | { 32 | "is successful with edesk", 33 | input{ 34 | Esk: "edesk1fddn27MaLcQVEdZpAYiyGQNm6UjtWiBfNP2ZenTy3CFsoSVJgeHM9pP9cvLJ2r5Xp2quQ5mYexW1LRKee2", 35 | Password: "password12345##", 36 | }, 37 | want{ 38 | false, 39 | "", 40 | "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF", 41 | "edpkuHMDkMz46HdRXYwom3xRwqk3zQ5ihWX4j8dwo2R2h8o4gPcbN5", 42 | "tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo", 43 | }, 44 | }, 45 | { 46 | "is successful with spesk", 47 | input{ 48 | Esk: "spesk24LjVwuCRhGsFYPGATnwaHAw7eZ6phDvGPntSHrSEVYstNpC3Zuq5k7oHTE1pAkVifNtJ1XW5UwCYcJC5BZ", 49 | Password: "abcd1234", 50 | }, 51 | want{ 52 | false, 53 | "", 54 | "spsk2psNeAQ88pKFnZikoZNb37zRbDmaGgQUtYrwwJZT3RcUspwL7N", 55 | "sppk7ZZADMMS4cwsu3odb7BAu9mx3DZYHmXWWL9GNhKremaJXqytGBc", 56 | "tz2TUwYWy5VP7ChX2xjXtGxxdfCnEQsotdeQ", 57 | }, 58 | }, 59 | { 60 | "is successful with p2esk", 61 | input{ 62 | Esk: "p2esk2UKLR5vLjQFjrotvStrYS6SXUFmGt9fkRjFydFwvFBJcbxWESvjfXtYKesgZaBHA7dx9MZJSwknhakFPZoc", 63 | Password: "abcd1234", 64 | }, 65 | want{ 66 | false, 67 | "", 68 | "p2sk3UumbKMrb6Wo1Jm5qTSMhUrCyAFTK4LMWgVma9njNLGc2Wcx9S", 69 | "p2pk6594Hd4VEVPydvK67c2GVikNXWjLiv2tkPUVvd8XMAXqd4CYxdK", 70 | "tz3fU9apdFnzoPhi4LB8AdxoiSVwLYM4kQ1F", 71 | }, 72 | }, 73 | // { 74 | // "is successful with mnemonic", 75 | // NewKeyInput{ 76 | // Kind: Ed25519, 77 | // Mnemonic: "normal dash crumble neutral reflect parrot know stairs culture fault check whale flock dog scout", 78 | // Password: "PYh8nXDQLB", 79 | // Email: "vksbjweo.qsrgfvbw@tezos.example.org", 80 | // }, 81 | // want{ 82 | // false, 83 | // "", 84 | // "edskRxB2DmoyZSyvhsqaJmw5CK6zYT7dbkUfEVSiQeWU1gw3ZMnC99QMMXru3imsbUrLhvuHktrymvNqhMxkhz7Y4LJAtevW5V", 85 | // "edpkvEoAbkdaGALxi2FfeefB8hUkMZ4J1UVwkzyumx2GvbVpkYUHnm", 86 | // "tz1Qny7jVMGiwRrP9FikRK95jTNbJcffTpx1", 87 | // }, 88 | // }, 89 | // { 90 | // "is successful with base58", 91 | // NewKeyInput{ 92 | // Kind: Ed25519, 93 | // EncodedString: "edskRxB2DmoyZSyvhsqaJmw5CK6zYT7dbkUfEVSiQeWU1gw3ZMnC99QMMXru3imsbUrLhvuHktrymvNqhMxkhz7Y4LJAtevW5V", 94 | // }, 95 | // want{ 96 | // false, 97 | // "", 98 | // "edskRxB2DmoyZSyvhsqaJmw5CK6zYT7dbkUfEVSiQeWU1gw3ZMnC99QMMXru3imsbUrLhvuHktrymvNqhMxkhz7Y4LJAtevW5V", 99 | // "edpkvEoAbkdaGALxi2FfeefB8hUkMZ4J1UVwkzyumx2GvbVpkYUHnm", 100 | // "tz1Qny7jVMGiwRrP9FikRK95jTNbJcffTpx1", 101 | // }, 102 | // }, 103 | } 104 | 105 | for _, tt := range cases { 106 | t.Run(tt.name, func(t *testing.T) { 107 | key, err := FromEncryptedSecret(tt.input.Esk, tt.input.Password) 108 | testutils.CheckErr(t, tt.want.wantErr, tt.want.containsErr, err) 109 | assert.Equal(t, tt.want.secretKey, key.GetSecretKey()) 110 | assert.Equal(t, tt.want.publicKey, key.PubKey.GetPublicKey()) 111 | assert.Equal(t, tt.want.address, key.PubKey.GetAddress()) 112 | }) 113 | } 114 | } 115 | 116 | func Test_FromBytes(t *testing.T) { 117 | privKey := []byte{117, 121, 196, 136, 31, 185, 152, 208, 67, 65, 123, 124, 4, 88, 42, 161, 81, 121, 241, 37, 197, 48, 62, 30, 229, 106, 150, 120, 3, 77, 149, 176} 118 | key, err := FromBytes(privKey, Ed25519) 119 | testutils.CheckErr(t, false, "", err) 120 | assert.Equal(t, "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF", key.GetSecretKey()) 121 | assert.Equal(t, "edpkuHMDkMz46HdRXYwom3xRwqk3zQ5ihWX4j8dwo2R2h8o4gPcbN5", key.PubKey.GetPublicKey()) 122 | assert.Equal(t, "tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo", key.PubKey.GetAddress()) 123 | } 124 | 125 | func Test_PubKey(t *testing.T) { 126 | v, err := hex.DecodeString("0000861299624c9a3b52be10762c64bac282b1c02316") 127 | testutils.CheckErr(t, false, "", err) 128 | 129 | address := tzcrypt.B58cencode(v[2:], []byte{6, 161, 159}) 130 | assert.Equal(t, "tz1XrwX7i9Nzh8e6UmG3VnFkAeoyWdTqDf3U", address) 131 | } 132 | 133 | func Test_FromHex(t *testing.T) { 134 | privKey := "7579c4881fb998d043417b7c04582aa15179f125c5303e1ee56a9678034d95b0" 135 | key, err := FromHex(privKey, Ed25519) 136 | testutils.CheckErr(t, false, "", err) 137 | assert.Equal(t, "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF", key.GetSecretKey()) 138 | assert.Equal(t, "edpkuHMDkMz46HdRXYwom3xRwqk3zQ5ihWX4j8dwo2R2h8o4gPcbN5", key.PubKey.GetPublicKey()) 139 | assert.Equal(t, "tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo", key.PubKey.GetAddress()) 140 | } 141 | 142 | // func Test_FromBase64(t *testing.T) { 143 | // privKey := base64.EncodeToString([]byte{117, 121, 196, 136, 31, 185, 152, 208, 67, 65, 123, 124, 4, 88, 42, 161, 81, 121, 241, 37, 197, 48, 62, 30, 229, 106, 150, 120, 3, 77, 149, 176}) 144 | // key, err := FromBase64(privKey, Ed25519) 145 | // testutils.CheckErr(t, false, "tt.want.containsErr", err) 146 | // assert.Equal(t, "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF", key.GetSecretKey()) 147 | // assert.Equal(t, "edpkuHMDkMz46HdRXYwom3xRwqk3zQ5ihWX4j8dwo2R2h8o4gPcbN5", key.PubKey.GetPublicKey()) 148 | // assert.Equal(t, "tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo", key.PubKey.GetPublicKeyHash()) 149 | // } 150 | 151 | func Test_FromBase58(t *testing.T) { 152 | privKey := "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF" 153 | key, err := FromBase58(privKey, Ed25519) 154 | testutils.CheckErr(t, false, "", err) 155 | assert.Equal(t, "edskRsPBsKuULoLTEQV2R9UbvSZbzFqvoESvp1mYyQJU8xi9mJamt88r5uTXbWQpVHjSiPWWtnoyqTCuSLQLxbEKUXfwwTccsF", key.GetSecretKey()) 156 | assert.Equal(t, "edpkuHMDkMz46HdRXYwom3xRwqk3zQ5ihWX4j8dwo2R2h8o4gPcbN5", key.PubKey.GetPublicKey()) 157 | assert.Equal(t, "tz1L8fUQLuwRuywTZUP5JUw9LL3kJa8LMfoo", key.PubKey.GetAddress()) 158 | } 159 | 160 | func Test_FromMnemonic(t *testing.T) { 161 | type want struct { 162 | wantErr bool 163 | containsErr string 164 | secretKey string 165 | publicKey string 166 | address string 167 | } 168 | 169 | type input struct { 170 | mnemonic string 171 | email string 172 | password string 173 | kind ECKind 174 | } 175 | 176 | cases := []struct { 177 | name string 178 | input input 179 | want want 180 | }{ 181 | { 182 | "is successful with mnemonic", 183 | input{ 184 | kind: Ed25519, 185 | mnemonic: "normal dash crumble neutral reflect parrot know stairs culture fault check whale flock dog scout", 186 | password: "PYh8nXDQLB", 187 | email: "vksbjweo.qsrgfvbw@tezos.example.org", 188 | }, 189 | want{ 190 | false, 191 | "", 192 | "edskRxB2DmoyZSyvhsqaJmw5CK6zYT7dbkUfEVSiQeWU1gw3ZMnC99QMMXru3imsbUrLhvuHktrymvNqhMxkhz7Y4LJAtevW5V", 193 | "edpkvEoAbkdaGALxi2FfeefB8hUkMZ4J1UVwkzyumx2GvbVpkYUHnm", 194 | "tz1Qny7jVMGiwRrP9FikRK95jTNbJcffTpx1", 195 | }, 196 | }, 197 | } 198 | 199 | for _, tt := range cases { 200 | t.Run(tt.name, func(t *testing.T) { 201 | key, err := FromMnemonic(tt.input.mnemonic, tt.input.email, tt.input.password, tt.input.kind) 202 | testutils.CheckErr(t, tt.want.wantErr, tt.want.containsErr, err) 203 | assert.Equal(t, tt.want.secretKey, key.GetSecretKey()) 204 | assert.Equal(t, tt.want.publicKey, key.PubKey.GetPublicKey()) 205 | assert.Equal(t, tt.want.address, key.PubKey.GetAddress()) 206 | }) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /rpc/.test-fixtures/ballot_list.json: -------------------------------------------------------------------------------- 1 | [{"pkh":"tz2TSvNTh2epDMhZHrw73nV9piBX7kLZ9K9m","ballot":"yay"},{"pkh":"tz2Q7Km98GPzV1JLNpkrQrSo5YUhPfDp6LmA","ballot":"yay"},{"pkh":"tz2PdGc7U5tiyqPgTSgqCDct94qd6ovQwP6u","ballot":"yay"},{"pkh":"tz2JMPu9yVKuX2Au8UUbp7YrKBZJSdYhgwwu","ballot":"yay"},{"pkh":"tz2FCNBrERXtaTtNX6iimR1UJ5JSDxvdHM93","ballot":"yay"},{"pkh":"tz3e75hU4EhDU3ukyJueh5v6UvEHzGwkg3yC","ballot":"yay"},{"pkh":"tz3bvNMQ95vfAYtG8193ymshqjSvmxiCUuR5","ballot":"pass"},{"pkh":"tz3bTdwZinP8U1JmSweNzVKhmwafqWmFWRfk","ballot":"pass"},{"pkh":"tz3bEQoFCZEEfZMskefZ8q8e4eiHH1pssRax","ballot":"nay"},{"pkh":"tz3adcvQaKXTCg12zbninqo3q8ptKKtDFTLv","ballot":"nay"},{"pkh":"tz3WMqdzXqRWXwyvj5Hp2H7QEepaUuS7vd9K","ballot":"pass"},{"pkh":"tz3VEZ4k6a4Wx42iyev6i2aVAptTRLEAivNN","ballot":"pass"},{"pkh":"tz3UoffC7FG7zfpmvmjUmUeAaHvzdcUvAj6r","ballot":"pass"},{"pkh":"tz3RDC3Jdn4j15J7bBHZd29EUee9gVB1CxD9","ballot":"pass"},{"pkh":"tz3RB4aoyjov4KEVRbuhvQ1CKJgBJMWhaeB8","ballot":"pass"},{"pkh":"tz3NExpXn9aPNZPorRE4SdjJ2RGrfbJgMAaV","ballot":"pass"},{"pkh":"tz1isXamBXpTUgbByQ6gXgZQg4GWNW7r6rKE","ballot":"nay"},{"pkh":"tz1iozzjFEBY4JNBiMpN9u9iDFAWNkqoMDuQ","ballot":"yay"},{"pkh":"tz1iZEKy4LaAjnTmn2RuGDf2iqdAQKnRi8kY","ballot":"yay"},{"pkh":"tz1iMAHAVpkCVegF9FLGWUpQQeiAHh4ffdLQ","ballot":"nay"},{"pkh":"tz1iJ4qgGTzyhaYEzd1RnC6duEkLBd1nzexh","ballot":"yay"},{"pkh":"tz1hx8hMmmeyDBi6WJgpKwK4n5S2qAEpavx2","ballot":"yay"},{"pkh":"tz1hfoePU9q6WEgk7Bf9P2yjZae2BoB3TruW","ballot":"yay"},{"pkh":"tz1hVmh8hDEM6ypZQY7vqnv1omkUfbPXRXCy","ballot":"yay"},{"pkh":"tz1hAYfexyzPGG6RhZZMpDvAHifubsbb6kgn","ballot":"yay"},{"pkh":"tz1h4GUAMweP7SNWzDgvMk3yRCAZyaP1MxZq","ballot":"yay"},{"pkh":"tz1gvrUnfTfEcRW6qcgB6FJdZnAxv4HG1rj9","ballot":"yay"},{"pkh":"tz1go7f6mEQfT2xX2LuHAqgnRGN6c2zHPf5c","ballot":"yay"},{"pkh":"tz1gk3TDbU7cJuiBRMhwQXVvgDnjsxuWhcEA","ballot":"yay"},{"pkh":"tz1gCx1V63bSaQnPZoQreqNgVLuFMzyMcqry","ballot":"yay"},{"pkh":"tz1g8vkmcde6sWKaG2NN9WKzCkDM6Rziq194","ballot":"yay"},{"pkh":"tz1g4SGrSVEYHgRZ6YbPU8C2fKNGTGwTDyqL","ballot":"yay"},{"pkh":"tz1fb7c66UwePkkfDXz4ajFaBP9hVNLdS7JJ","ballot":"yay"},{"pkh":"tz1fZ767VDbqx4DeKiFswPSHh513f51mKEUZ","ballot":"yay"},{"pkh":"tz1fWJaG5uj6bQZ19bCSLjELJz6g2J6inUL1","ballot":"yay"},{"pkh":"tz1fJHFn6sWEd3NnBPngACuw2dggTv6nQZ7g","ballot":"yay"},{"pkh":"tz1f2ff2n1nBuwRZPh6CoDhtsYZW1Q47xK7b","ballot":"nay"},{"pkh":"tz1ei4WtWEMEJekSv8qDnu9PExG6Q8HgRGr3","ballot":"yay"},{"pkh":"tz1eZwq8b5cvE2bPKokatLkVMzkxz24z3Don","ballot":"yay"},{"pkh":"tz1eLbDXYceRsPZoPmaJXZgQ6pzgnTQvZtpo","ballot":"yay"},{"pkh":"tz1eEnQhbwf6trb8Q8mPb2RaPkNk2rN7BKi8","ballot":"yay"},{"pkh":"tz1eE4ERseiJfLTy3ydsdiitpCMbRRHEGhqM","ballot":"nay"},{"pkh":"tz1eDDuQBEgwvc6tbnnCVrnr12tvrd6gBTpx","ballot":"yay"},{"pkh":"tz1eCs8nFQiKTqwcqQjKyz8QGMCQ4JAXbPS8","ballot":"yay"},{"pkh":"tz1dhwJY5B1jKdkF7zcyu1v6EBEQjpGmAp1E","ballot":"nay"},{"pkh":"tz1dhifsh42PxTXJ9ihbu8dSLvKPVNs4TbKb","ballot":"yay"},{"pkh":"tz1dSmmQY4HUzu39PRzMRZkBFWQVGaiFkdYR","ballot":"nay"},{"pkh":"tz1d6Fx42mYgVFnHUW8T8A7WBfJ6nD9pVok8","ballot":"yay"},{"pkh":"tz1d2cWkRp2bvCRwo5LmJe5S5iKQciWUpDag","ballot":"pass"},{"pkh":"tz1ciir8LAtaWNE8VXVn2J1RwDb7scGFNF5c","ballot":"yay"},{"pkh":"tz1ceXYbx1zrYQ72Mcvi5pZtzt7reHaDW5B6","ballot":"yay"},{"pkh":"tz1cb8xcmJWcdVU7cNAd93MfEReorvP52P8x","ballot":"yay"},{"pkh":"tz1cZfFQpcYhwDp7y1njZXDsZqCrn2NqmVof","ballot":"yay"},{"pkh":"tz1cYufsxHXJcvANhvS55h3aY32a9BAFB494","ballot":"yay"},{"pkh":"tz1cXxvRg7wYwmsv9LWPivANBRAHU4GW2y5d","ballot":"yay"},{"pkh":"tz1cX93Q3KsiTADpCC4f12TBvAmS5tw7CW19","ballot":"yay"},{"pkh":"tz1cUq85w3an1vcwE8Xy2LbjRuSwN3yMNdHF","ballot":"pass"},{"pkh":"tz1cSoWttzYi9taqyHfcKS86b5M31SoaTQdg","ballot":"yay"},{"pkh":"tz1c8TSSGtVtg7ANrNVY7G4KnLn7iKdRnUVk","ballot":"yay"},{"pkh":"tz1c7pVR4w3KSQarJdBeh4NS2WxMUFBy1rHQ","ballot":"yay"},{"pkh":"tz1c3Wh8gNMMsYwZd67JndQpYxdaaPUV27E7","ballot":"yay"},{"pkh":"tz1bsdrs36X5BTH83RHUBdUouKLJZK8d2oBa","ballot":"yay"},{"pkh":"tz1bkg7rynMXVcjomoe3diB4URfv8GU2GAcw","ballot":"yay"},{"pkh":"tz1bkKTY9Y3rTsHbpr2fbGUCRm736LLquQfM","ballot":"yay"},{"pkh":"tz1beQpeX792s7RW3RXFzMBtYWTYwfNhBBPS","ballot":"yay"},{"pkh":"tz1bakeKFwqmtLBzghw8CFnqFvRxLj849Vfg","ballot":"yay"},{"pkh":"tz1bacP88iSnWHAVUBQShtE4ZnUGYHUpGVBM","ballot":"nay"},{"pkh":"tz1bRaSjKZrSrSeQHBDiCqjKXqtZYZM1t8FW","ballot":"yay"},{"pkh":"tz1bLwpPfr3xqy1gWBF4sGvv8bLJyPHR11kx","ballot":"yay"},{"pkh":"tz1bHzftcTKZMTZgLLtnrXydCm6UEqf4ivca","ballot":"yay"},{"pkh":"tz1b7YSEeNRqgmjuX4d4aiai2sQTF4A7WBZf","ballot":"yay"},{"pkh":"tz1auX9i2DajBrtafL6UpHk2T6GAmtcskX1Y","ballot":"yay"},{"pkh":"tz1aqcYgG6NuViML5vdWhohHJBYxcDVLNUsE","ballot":"nay"},{"pkh":"tz1aehDcppdSz7Af2PtXvNQtqpeMfvFFYFHf","ballot":"yay"},{"pkh":"tz1abmz7jiCV2GH2u81LRrGgAFFgvQgiDiaf","ballot":"nay"},{"pkh":"tz1abTjX2tjtMdaq5VCzkDtBnMSCFPW2oRPa","ballot":"yay"},{"pkh":"tz1aXD7Jigrt3PBYSpHSg5Rr33SexMmmM5Ys","ballot":"yay"},{"pkh":"tz1aKxnrzx5PXZJe7unufEswVRCMU9yafmfb","ballot":"yay"},{"pkh":"tz1aEsrxrhTYNcg897HjSWnJ5yjR95pxVD1d","ballot":"yay"},{"pkh":"tz1a51WYsAsoh4fs4A4x5aCN6Za26mpfDfTi","ballot":"yay"},{"pkh":"tz1Zhv3RkfU2pHrmaiDyxp7kFZpZrUCu1CiF","ballot":"yay"},{"pkh":"tz1ZeKJXXhwjdomsgCeKu5VoyLtvBw9EXpkh","ballot":"yay"},{"pkh":"tz1Zcxkfa5jKrRbBThG765GP29bUCU3C4ok5","ballot":"yay"},{"pkh":"tz1ZRWFLgT9sz8iFi1VYWPfRYeUvUSFAaDao","ballot":"yay"},{"pkh":"tz1ZNWFe3LmEJYTydctcgD6a5Apemwdtimn4","ballot":"yay"},{"pkh":"tz1Z9MUGuBWyYvbQDVrY5ShivapzkCtUk4DT","ballot":"yay"},{"pkh":"tz1Z3KCf8CLGAYfvVWPEr562jDDyWkwNF7sT","ballot":"yay"},{"pkh":"tz1Z2jXfEXL7dXhs6bsLmyLFLfmAkXBzA9WE","ballot":"yay"},{"pkh":"tz1Yju7jmmsaUiG9qQLoYv35v5pHgnWoLWbt","ballot":"yay"},{"pkh":"tz1YdCPrYbksK7HCoYKDyzgfXwY16Fy9rrGa","ballot":"yay"},{"pkh":"tz1Y42aKCk53vMbaJNpf1hBg1rznGdBxHJ5C","ballot":"yay"},{"pkh":"tz1XmcSGG8VEhkcejyfm5VEh6BBrChae4B4U","ballot":"yay"},{"pkh":"tz1XfAjZyaLdceHnZxbMYop7g7kWKPut4PR7","ballot":"yay"},{"pkh":"tz1XXqtv3NxSf1PBC6TrqAV9ZgmXYoQ4tSH1","ballot":"yay"},{"pkh":"tz1XXayQohB8XRXN7kMoHbf2NFwNiH3oMRQQ","ballot":"yay"},{"pkh":"tz1XGG8Vuj4eX89t1coNNhLhorfJ6b9xMppa","ballot":"yay"},{"pkh":"tz1WpeqFaBG9Jm73Dmgqamy8eF8NWLz9JCoY","ballot":"yay"},{"pkh":"tz1WnfXMPaNTBmH7DBPwqCWs9cPDJdkGBTZ8","ballot":"yay"},{"pkh":"tz1WCd2jm4uSt4vntk4vSuUWoZQGhLcDuR9q","ballot":"yay"},{"pkh":"tz1W5VkdB5s7ENMESVBtwyt9kyvLqPcUczRT","ballot":"yay"},{"pkh":"tz1Vyuu4EJ5Nym4JcrfRLnp3hpaq1DSEp1Ke","ballot":"yay"},{"pkh":"tz1VxbHcvqoiiZ3Fbc4oVeDhZQeoFRiWMaqN","ballot":"yay"},{"pkh":"tz1VmiY38m3y95HqQLjMwqnMS7sdMfGomzKi","ballot":"yay"},{"pkh":"tz1Vd1rXpV8hTHbFXCXN3c3qzCsgcU5BZw1e","ballot":"yay"},{"pkh":"tz1VceyYUpq1gk5dtp6jXQRtCtY8hm5DKt72","ballot":"yay"},{"pkh":"tz1VHmaNfUC9BBcm851g2fhgSUq8NxFapuCA","ballot":"yay"},{"pkh":"tz1VHFxUuBhwopxC9YC9gm5s2MHBHLyCtvN1","ballot":"yay"},{"pkh":"tz1V4qCyvPKZ5UeqdH14HN42rxvNPQfc9UZg","ballot":"yay"},{"pkh":"tz1V3yg82mcrPJbegqVCPn6bC8w1CSTRp3f8","ballot":"yay"},{"pkh":"tz1V2FYediKBAEaTpXXJBSjuQpjkyCzrTSiE","ballot":"nay"},{"pkh":"tz1UuAD4PFzdSEiX3bGPEsq56MCDVR5wa4iJ","ballot":"yay"},{"pkh":"tz1UNkf4sWzC5vsKP7FnejSeJsXdc6z8Kzk4","ballot":"yay"},{"pkh":"tz1UJvHTgpVzcKWhTazGxVcn5wsHru5Gietg","ballot":"yay"},{"pkh":"tz1UGDXghfW4Z7UhobBkfQTayMrVssCgsGGQ","ballot":"yay"},{"pkh":"tz1U2ufqFdVkN2RdYormwHtgm3ityYY1uqft","ballot":"yay"},{"pkh":"tz1TwC61U87viDeZvkUXaQmduG9LhUgYmAxi","ballot":"yay"},{"pkh":"tz1TcH4Nb3aHNDJ7CGZhU7jgAK1BkSP4Lxds","ballot":"yay"},{"pkh":"tz1TRspM5SeZpaQUhzByXbEvqKF1vnCM2YTK","ballot":"yay"},{"pkh":"tz1TRqbYbUf2GyrjErf3hBzgBJPzW8y36qEs","ballot":"yay"},{"pkh":"tz1TPA3qbr3BKUwMjZE1j4MQbjSfTfgizy8J","ballot":"yay"},{"pkh":"tz1TDSmoZXwVevLTEvKCTHWpomG76oC9S2fJ","ballot":"yay"},{"pkh":"tz1StDhtR8g2eMs4UyeT45fQeG7MQRPWyPoM","ballot":"yay"},{"pkh":"tz1Scdr2HsZiQjc7bHMeBbmDRXYVvdhjJbBh","ballot":"yay"},{"pkh":"tz1SYq214SCBy9naR6cvycQsYcUGpBqQAE8d","ballot":"pass"},{"pkh":"tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc","ballot":"yay"},{"pkh":"tz1S7GgVV4FPEGUVzepKBwx22DyNikdpa4X6","ballot":"yay"},{"pkh":"tz1S4h14wgWM4n3J12b2dQdvkYQJtSGEHRHd","ballot":"yay"},{"pkh":"tz1S1Aew75hMrPUymqenKfHo8FspppXKpW7h","ballot":"yay"},{"pkh":"tz1RWg4v4qwi3p1XjTxQ7gYRyQD7qPUZuaR7","ballot":"yay"},{"pkh":"tz1RV1MBbZMR68tacosb7Mwj6LkbPSUS1er1","ballot":"yay"},{"pkh":"tz1RDJuMvXSXwtXTkSrgVCfBw5mM58iT1RC9","ballot":"pass"},{"pkh":"tz1RCFbB9GpALpsZtu6J58sb74dm8qe6XBzv","ballot":"yay"},{"pkh":"tz1RAzdkp1x5ZDqms4ZUrSdfJuUYGH9JTPK2","ballot":"nay"},{"pkh":"tz1R6Ej25VSerE3MkSoEEeBjKHCDTFbpKuSX","ballot":"yay"},{"pkh":"tz1QgrhpYpAqPjDCcCF65vCH4f9jw25YxcU4","ballot":"yay"},{"pkh":"tz1QccGQsmt7WMnbVX9inJQgEuQGeMXddfjp","ballot":"yay"},{"pkh":"tz1QLXqnfN51dkjeghXvKHkJfhvGiM5gK4tc","ballot":"yay"},{"pkh":"tz1PeZx7FXy7QRuMREGXGxeipb24RsMMzUNe","ballot":"yay"},{"pkh":"tz1PWCDnz783NNGGQjEFFsHtrcK5yBW4E2rm","ballot":"yay"},{"pkh":"tz1PMWrbSda1RvsRi8Xq7GRd3P7739jtyaM8","ballot":"yay"},{"pkh":"tz1P2Po7YM526ughEsRbY4oR9zaUPDZjxFrb","ballot":"yay"},{"pkh":"tz1NuUu12WHdW3jHqvkujGHsiTi46Wvw1ci1","ballot":"yay"},{"pkh":"tz1NortRftucvAkD1J58L32EhSVrQEWJCEnB","ballot":"yay"},{"pkh":"tz1NoYvKjXTzTk54VpLxBfouJ33J8jwKPPvw","ballot":"yay"},{"pkh":"tz1Nn14BBsDULrPXtkM9UQeXaE4iqJhmqmK5","ballot":"yay"},{"pkh":"tz1NkFRjmkqqcGkAhqe78fdgemDNKXvL7Bod","ballot":"yay"},{"pkh":"tz1Nioy11zxFuyr5TtYeLP1rn3QDMRNbhGCr","ballot":"yay"},{"pkh":"tz1NTYyCYdh4HFvKGE4XYeJAFWhEgN68h91h","ballot":"yay"},{"pkh":"tz1NRGxXV9h6SdNaZLcgmjuLx3hyy2f8YoGN","ballot":"nay"},{"pkh":"tz1NMdMmWZN8QPB8pY4ddncACDg1cHi1xD2e","ballot":"yay"},{"pkh":"tz1MvAMJZ1ZZbFhZnwUtaiF2sV4WxxceWPLz","ballot":"yay"},{"pkh":"tz1MivraUX9U6nmGAQkm7XkrNVmsPExEUT1W","ballot":"yay"},{"pkh":"tz1Mhf5d2jNWoXov48HzyFJkzCC8ywiXRmBo","ballot":"yay"},{"pkh":"tz1MecudVJnFZN5FSrriu8ULz2d6dDTR7KaM","ballot":"yay"},{"pkh":"tz1MXFrtZoaXckE41bjUCSjAjAap3AFDSr3N","ballot":"yay"},{"pkh":"tz1MQJPGNMijnXnVoBENFz9rUhaPt3S7rWoz","ballot":"yay"},{"pkh":"tz1MJx9vhaNRSimcuXPK2rW4fLccQnDAnVKJ","ballot":"yay"},{"pkh":"tz1MBidfvWhJ64MuJapKExcP5SV4HQWyiJwS","ballot":"yay"},{"pkh":"tz1LnWa4AiFCjMezTgSNa65QFoo7DQGEWgEU","ballot":"yay"},{"pkh":"tz1Lhf4J9Qxoe3DZ2nfe8FGDnvVj7oKjnMY6","ballot":"yay"},{"pkh":"tz1Ldzz6k1BHdhuKvAtMRX7h5kJSMHESMHLC","ballot":"yay"},{"pkh":"tz1Lc87p6zRaDwzJs9kHvdpm7XzeWE8QTwVB","ballot":"yay"},{"pkh":"tz1LUZNxLonRmotkLszDcNQLBMkMj42x7DdY","ballot":"nay"},{"pkh":"tz1LLNkQK4UQV6QcFShiXJ2vT2ELw449MzAA","ballot":"yay"},{"pkh":"tz1LH4L6XYT2JNPhvWYC4Zq3XEiGgEwzNRvo","ballot":"yay"},{"pkh":"tz1LBEKXaxQbd5Gtzbc1ATCwc3pppu81aWGc","ballot":"yay"},{"pkh":"tz1KksC8RvjUWAbXYJuNrUbontHGor26Cztk","ballot":"yay"},{"pkh":"tz1KfEsrtDaA1sX7vdM4qmEPWuSytuqCDp5j","ballot":"nay"}] -------------------------------------------------------------------------------- /rpc/.test-fixtures/entrypoints.json: -------------------------------------------------------------------------------- 1 | { 2 | "entrypoints": { 3 | "xtzToToken": { 4 | "prim": "pair", 5 | "args": [ 6 | { 7 | "prim": "address", 8 | "annots": [ 9 | ":to" 10 | ] 11 | }, 12 | { 13 | "prim": "pair", 14 | "args": [ 15 | { 16 | "prim": "nat", 17 | "annots": [ 18 | ":minTokensBought" 19 | ] 20 | }, 21 | { 22 | "prim": "timestamp", 23 | "annots": [ 24 | ":deadline" 25 | ] 26 | } 27 | ] 28 | } 29 | ] 30 | }, 31 | "updateTokenPoolInternal": { 32 | "prim": "nat" 33 | }, 34 | "updateTokenPool": { 35 | "prim": "key_hash" 36 | }, 37 | "tokenToXtz": { 38 | "prim": "pair", 39 | "args": [ 40 | { 41 | "prim": "pair", 42 | "args": [ 43 | { 44 | "prim": "address", 45 | "annots": [ 46 | ":owner" 47 | ] 48 | }, 49 | { 50 | "prim": "address", 51 | "annots": [ 52 | ":to" 53 | ] 54 | } 55 | ] 56 | }, 57 | { 58 | "prim": "pair", 59 | "args": [ 60 | { 61 | "prim": "nat", 62 | "annots": [ 63 | ":tokensSold" 64 | ] 65 | }, 66 | { 67 | "prim": "pair", 68 | "args": [ 69 | { 70 | "prim": "mutez", 71 | "annots": [ 72 | ":minXtzBought" 73 | ] 74 | }, 75 | { 76 | "prim": "timestamp", 77 | "annots": [ 78 | ":deadline" 79 | ] 80 | } 81 | ] 82 | } 83 | ] 84 | } 85 | ] 86 | }, 87 | "tokenToToken": { 88 | "prim": "pair", 89 | "args": [ 90 | { 91 | "prim": "pair", 92 | "args": [ 93 | { 94 | "prim": "address", 95 | "annots": [ 96 | ":outputDexterContract" 97 | ] 98 | }, 99 | { 100 | "prim": "pair", 101 | "args": [ 102 | { 103 | "prim": "nat", 104 | "annots": [ 105 | ":minTokensBought" 106 | ] 107 | }, 108 | { 109 | "prim": "address", 110 | "annots": [ 111 | ":owner" 112 | ] 113 | } 114 | ] 115 | } 116 | ] 117 | }, 118 | { 119 | "prim": "pair", 120 | "args": [ 121 | { 122 | "prim": "address", 123 | "annots": [ 124 | ":to" 125 | ] 126 | }, 127 | { 128 | "prim": "pair", 129 | "args": [ 130 | { 131 | "prim": "nat", 132 | "annots": [ 133 | ":tokensSold" 134 | ] 135 | }, 136 | { 137 | "prim": "timestamp", 138 | "annots": [ 139 | ":deadline" 140 | ] 141 | } 142 | ] 143 | } 144 | ] 145 | } 146 | ] 147 | }, 148 | "setManager": { 149 | "prim": "address" 150 | }, 151 | "setBaker": { 152 | "prim": "pair", 153 | "args": [ 154 | { 155 | "prim": "option", 156 | "args": [ 157 | { 158 | "prim": "key_hash" 159 | } 160 | ] 161 | }, 162 | { 163 | "prim": "bool" 164 | } 165 | ] 166 | }, 167 | "removeLiquidity": { 168 | "prim": "pair", 169 | "args": [ 170 | { 171 | "prim": "pair", 172 | "args": [ 173 | { 174 | "prim": "address", 175 | "annots": [ 176 | ":owner" 177 | ] 178 | }, 179 | { 180 | "prim": "pair", 181 | "args": [ 182 | { 183 | "prim": "address", 184 | "annots": [ 185 | ":to" 186 | ] 187 | }, 188 | { 189 | "prim": "nat", 190 | "annots": [ 191 | ":lqtBurned" 192 | ] 193 | } 194 | ] 195 | } 196 | ] 197 | }, 198 | { 199 | "prim": "pair", 200 | "args": [ 201 | { 202 | "prim": "mutez", 203 | "annots": [ 204 | ":minXtzWithdrawn" 205 | ] 206 | }, 207 | { 208 | "prim": "pair", 209 | "args": [ 210 | { 211 | "prim": "nat", 212 | "annots": [ 213 | ":minTokensWithdrawn" 214 | ] 215 | }, 216 | { 217 | "prim": "timestamp", 218 | "annots": [ 219 | ":deadline" 220 | ] 221 | } 222 | ] 223 | } 224 | ] 225 | } 226 | ] 227 | }, 228 | "default": { 229 | "prim": "unit" 230 | }, 231 | "approve": { 232 | "prim": "pair", 233 | "args": [ 234 | { 235 | "prim": "address", 236 | "annots": [ 237 | ":spender" 238 | ] 239 | }, 240 | { 241 | "prim": "pair", 242 | "args": [ 243 | { 244 | "prim": "nat", 245 | "annots": [ 246 | ":allowance" 247 | ] 248 | }, 249 | { 250 | "prim": "nat", 251 | "annots": [ 252 | ":currentAllowance" 253 | ] 254 | } 255 | ] 256 | } 257 | ] 258 | }, 259 | "addLiquidity": { 260 | "prim": "pair", 261 | "args": [ 262 | { 263 | "prim": "pair", 264 | "args": [ 265 | { 266 | "prim": "address", 267 | "annots": [ 268 | ":owner" 269 | ] 270 | }, 271 | { 272 | "prim": "nat", 273 | "annots": [ 274 | ":minLqtMinted" 275 | ] 276 | } 277 | ] 278 | }, 279 | { 280 | "prim": "pair", 281 | "args": [ 282 | { 283 | "prim": "nat", 284 | "annots": [ 285 | ":maxTokensDeposited" 286 | ] 287 | }, 288 | { 289 | "prim": "timestamp", 290 | "annots": [ 291 | ":deadline" 292 | ] 293 | } 294 | ] 295 | } 296 | ] 297 | } 298 | } 299 | } -------------------------------------------------------------------------------- /rpc/votes.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | 8 | "github.com/go-resty/resty/v2" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | /* 13 | BallotList represents a list of casted ballots in a block. 14 | 15 | RPC: 16 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-ballot-list 17 | */ 18 | type BallotList []struct { 19 | PublicKeyHash string `json:"pkh"` 20 | Ballot string `json:"ballot"` 21 | } 22 | 23 | /* 24 | BallotList returns ballots casted so far during a voting period. 25 | 26 | Path: 27 | ..//votes/ballot_list (GET) 28 | 29 | RPC: 30 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-ballot-list 31 | */ 32 | func (c *Client) BallotList(blockID BlockID) (*resty.Response, BallotList, error) { 33 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/ballot_list", c.chain, blockID.ID())) 34 | if err != nil { 35 | return nil, BallotList{}, errors.Wrapf(err, "failed to get ballot list") 36 | } 37 | 38 | var ballotList BallotList 39 | err = json.Unmarshal(resp.Body(), &ballotList) 40 | if err != nil { 41 | return resp, BallotList{}, errors.Wrapf(err, "failed to get ballot list: failed to parse json") 42 | } 43 | 44 | return resp, ballotList, nil 45 | } 46 | 47 | /* 48 | Ballots represents a ballot total. 49 | 50 | RPC: 51 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-ballots 52 | */ 53 | type Ballots struct { 54 | Yay int `json:"yay"` 55 | Nay int `json:"nay"` 56 | Pass int `json:"pass"` 57 | } 58 | 59 | /* 60 | Ballots returns sum of ballots casted so far during a voting period. 61 | 62 | Path: 63 | ..//votes/ballots (GET) 64 | 65 | RPC: 66 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-ballots 67 | */ 68 | func (c *Client) Ballots(blockID BlockID) (*resty.Response, Ballots, error) { 69 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/ballots", c.chain, blockID.ID())) 70 | if err != nil { 71 | return nil, Ballots{}, errors.Wrapf(err, "failed to get ballots") 72 | } 73 | 74 | var ballots Ballots 75 | err = json.Unmarshal(resp.Body(), &ballots) 76 | if err != nil { 77 | return resp, Ballots{}, errors.Wrapf(err, "failed to get ballots: failed to parse json") 78 | } 79 | 80 | return resp, ballots, nil 81 | } 82 | 83 | /* 84 | VotingPeriod is the the voting period (index, kind, starting position) and related information (position, remaining) of the interrogated block. 85 | 86 | Path: 87 | ..//votes/current_period (GET) 88 | 89 | RPC: 90 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-current-period 91 | */ 92 | type VotingPeriod struct { 93 | VotingPeriod struct { 94 | Index int `json:"index"` 95 | Kind string `json:"kind"` 96 | StartPosition int `json:"start_position"` 97 | } `json:"voting_period"` 98 | Position int `json:"position"` 99 | Remaining int `json:"remaining"` 100 | } 101 | 102 | /* 103 | CurrentPeriod returns the voting period (index, kind, starting position) and related information (position, remaining) of the interrogated block. 104 | 105 | Path: 106 | ..//votes/current_period (GET) 107 | 108 | RPC: 109 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-current-period 110 | */ 111 | func (c *Client) CurrentPeriod(blockID BlockID) (*resty.Response, VotingPeriod, error) { 112 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/current_period", c.chain, blockID.ID())) 113 | if err != nil { 114 | return resp, VotingPeriod{}, errors.Wrapf(err, "failed to get current period") 115 | } 116 | 117 | var currentPeriod VotingPeriod 118 | err = json.Unmarshal(resp.Body(), ¤tPeriod) 119 | if err != nil { 120 | return resp, VotingPeriod{}, errors.Wrapf(err, "failed to get current period: failed to parse json") 121 | } 122 | 123 | return resp, currentPeriod, nil 124 | } 125 | 126 | /* 127 | CurrentPeriodKind returns the current period kind. 128 | 129 | Path: 130 | ..//votes/current_period_kind (GET) 131 | 132 | RPC: 133 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-current-period-kind 134 | */ 135 | func (c *Client) CurrentPeriodKind(blockID BlockID) (*resty.Response, string, error) { 136 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/current_period_kind", c.chain, blockID.ID())) 137 | if err != nil { 138 | return resp, "", errors.Wrapf(err, "failed to get current period kind") 139 | } 140 | 141 | var currentPeriodKind string 142 | err = json.Unmarshal(resp.Body(), ¤tPeriodKind) 143 | if err != nil { 144 | return resp, "", errors.Wrapf(err, "failed to get current period kind: failed to parse json") 145 | } 146 | 147 | return resp, currentPeriodKind, nil 148 | } 149 | 150 | /* 151 | CurrentProposal returns the current proposal under evaluation. 152 | 153 | Path: 154 | ..//votes/current_proposal (GET) 155 | 156 | RPC: 157 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-current-proposal 158 | */ 159 | func (c *Client) CurrentProposal(blockID BlockID) (*resty.Response, string, error) { 160 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/current_proposal", c.chain, blockID.ID())) 161 | if err != nil { 162 | return resp, "", errors.Wrapf(err, "failed to get current proposal") 163 | } 164 | 165 | var currentProposal string 166 | err = json.Unmarshal(resp.Body(), ¤tProposal) 167 | if err != nil { 168 | return resp, "", errors.Wrapf(err, "failed to get current proposal: failed to parse json") 169 | } 170 | 171 | return resp, currentProposal, nil 172 | } 173 | 174 | /* 175 | CurrentQuorum returns the current expected quorum. 176 | 177 | Path: 178 | ..//votes/current_proposal (GET) 179 | 180 | RPC: 181 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-current-quorum 182 | */ 183 | func (c *Client) CurrentQuorum(blockID BlockID) (*resty.Response, int, error) { 184 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/current_quorum", c.chain, blockID.ID())) 185 | if err != nil { 186 | return resp, 0, errors.Wrapf(err, "failed to get current quorum") 187 | } 188 | 189 | var currentQuorum int 190 | err = json.Unmarshal(resp.Body(), ¤tQuorum) 191 | if err != nil { 192 | return resp, 0, errors.Wrapf(err, "failed to get current quorum: failed to parse json") 193 | } 194 | 195 | return resp, currentQuorum, nil 196 | } 197 | 198 | /* 199 | Listings represents a list of delegates with their voting weight, in number of rolls. 200 | 201 | RPC: 202 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-listings 203 | */ 204 | type Listings []struct { 205 | PublicKeyHash string `json:"pkh"` 206 | Rolls int `json:"rolls"` 207 | } 208 | 209 | /* 210 | Listings returns a list of delegates with their voting weight, in number of rolls. 211 | 212 | Path: 213 | ..//votes/listings (GET) 214 | 215 | RPC: 216 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-listings 217 | */ 218 | func (c *Client) Listings(blockID BlockID) (*resty.Response, Listings, error) { 219 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/listings", c.chain, blockID.ID())) 220 | if err != nil { 221 | return resp, Listings{}, errors.Wrapf(err, "failed to get listings") 222 | } 223 | 224 | var listings Listings 225 | err = json.Unmarshal(resp.Body(), &listings) 226 | if err != nil { 227 | return resp, Listings{}, errors.Wrapf(err, "failed to get listings: failed to parse json") 228 | } 229 | 230 | return resp, listings, nil 231 | } 232 | 233 | /* 234 | Proposals is the list of proposals with number of supporters. 235 | 236 | RPC: 237 | https://tezos.gitlab.io/api/rpc.html#get-block-id-votes-proposals 238 | */ 239 | type Proposals []struct { 240 | Hash string 241 | Supporters int 242 | } 243 | 244 | // UnmarshalJSON satisfies the json.Marshaler 245 | func (p *Proposals) UnmarshalJSON(b []byte) error { 246 | var out [][]interface{} 247 | if err := json.Unmarshal(b, &out); err != nil { 248 | return err 249 | } 250 | 251 | var proposals Proposals 252 | for _, x := range out { 253 | if len(x) != 2 { 254 | return errors.New("unexpected bytes") 255 | } 256 | 257 | hash := fmt.Sprintf("%v", x[0]) 258 | supportersStr := fmt.Sprintf("%v", x[1]) 259 | supporters, err := strconv.Atoi(supportersStr) 260 | if err != nil { 261 | return errors.New("unexpected bytes") 262 | } 263 | 264 | proposals = append(proposals, struct { 265 | Hash string 266 | Supporters int 267 | }{ 268 | Hash: hash, 269 | Supporters: supporters, 270 | }) 271 | } 272 | 273 | p = &proposals 274 | return nil 275 | } 276 | 277 | /* 278 | Proposals returns a list of proposals with number of supporters. 279 | 280 | Path: 281 | ..//votes/proposals (GET) 282 | 283 | RPC: 284 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-proposals 285 | */ 286 | func (c *Client) Proposals(blockID BlockID) (*resty.Response, Proposals, error) { 287 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/proposals", c.chain, blockID.ID())) 288 | if err != nil { 289 | return resp, Proposals{}, errors.Wrapf(err, "failed to get proposals") 290 | } 291 | 292 | var proposals Proposals 293 | err = json.Unmarshal(resp.Body(), &proposals) 294 | if err != nil { 295 | return resp, Proposals{}, errors.Wrapf(err, "failed to get proposals: failed to parse json") 296 | } 297 | 298 | return resp, proposals, nil 299 | } 300 | 301 | /* 302 | SuccessorPeriod returns the voting period (index, kind, starting position) and related information (position, remaining) of the next block. 303 | 304 | Path: 305 | ..//votes/successor_period (GET) 306 | 307 | RPC: 308 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-successor-period 309 | */ 310 | func (c *Client) SuccessorPeriod(blockID BlockID) (*resty.Response, VotingPeriod, error) { 311 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/proposals", c.chain, blockID.ID())) 312 | if err != nil { 313 | return resp, VotingPeriod{}, errors.Wrapf(err, "failed to get successor period") 314 | } 315 | 316 | var votingPeriod VotingPeriod 317 | err = json.Unmarshal(resp.Body(), &votingPeriod) 318 | if err != nil { 319 | return resp, VotingPeriod{}, errors.Wrapf(err, "failed to get successor period: failed to parse json") 320 | } 321 | 322 | return resp, votingPeriod, nil 323 | } 324 | 325 | /* 326 | TotalVotingPower returns the total number of rolls for the delegates in the voting listings. 327 | 328 | Path: 329 | ..//votes/current_proposal (GET) 330 | 331 | RPC: 332 | https://tezos.gitlab.io/008/rpc.html#get-block-id-votes-total-voting-power 333 | */ 334 | func (c *Client) TotalVotingPower(blockID BlockID) (*resty.Response, int, error) { 335 | resp, err := c.get(fmt.Sprintf("/chains/%s/blocks/%s/votes/total_voting_power", c.chain, blockID.ID())) 336 | if err != nil { 337 | return resp, 0, errors.Wrapf(err, "failed to get total voting power") 338 | } 339 | 340 | var votingPower int 341 | err = json.Unmarshal(resp.Body(), &votingPower) 342 | if err != nil { 343 | return resp, 0, errors.Wrapf(err, "failed to get total voting power: failed to parse json") 344 | } 345 | 346 | return resp, votingPower, nil 347 | } 348 | -------------------------------------------------------------------------------- /rpc/votes_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/goat-systems/go-tezos/v4/rpc" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func Test_BallotList(t *testing.T) { 13 | goldenBallotList := getResponse(ballotList).(*rpc.BallotList) 14 | 15 | type want struct { 16 | wantErr bool 17 | containsErr string 18 | ballotList rpc.BallotList 19 | } 20 | 21 | cases := []struct { 22 | name string 23 | inputHanler http.Handler 24 | want 25 | }{ 26 | { 27 | "handles RPC error", 28 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallotList, readResponse(rpcerrors)}, blankHandler)), 29 | want{ 30 | true, 31 | "failed to get ballot list", 32 | rpc.BallotList{}, 33 | }, 34 | }, 35 | { 36 | "handles failure to unmarshal", 37 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallotList, []byte(`junk`)}, blankHandler)), 38 | want{ 39 | true, 40 | "failed to get ballot list: failed to parse json", 41 | rpc.BallotList{}, 42 | }, 43 | }, 44 | { 45 | "is successful", 46 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallotList, readResponse(ballotList)}, blankHandler)), 47 | want{ 48 | false, 49 | "", 50 | *goldenBallotList, 51 | }, 52 | }, 53 | } 54 | 55 | for _, tt := range cases { 56 | t.Run(tt.name, func(t *testing.T) { 57 | server := httptest.NewServer(tt.inputHanler) 58 | defer server.Close() 59 | 60 | r, err := rpc.New(server.URL) 61 | assert.Nil(t, err) 62 | 63 | _, ballotList, err := r.BallotList(&rpc.BlockIDHead{}) 64 | checkErr(t, tt.wantErr, tt.containsErr, err) 65 | assert.Equal(t, tt.want.ballotList, ballotList) 66 | }) 67 | } 68 | } 69 | 70 | func Test_Ballots(t *testing.T) { 71 | goldenBallots := getResponse(ballots).(*rpc.Ballots) 72 | 73 | type want struct { 74 | wantErr bool 75 | containsErr string 76 | ballots rpc.Ballots 77 | } 78 | 79 | cases := []struct { 80 | name string 81 | inputHanler http.Handler 82 | want 83 | }{ 84 | { 85 | "handles RPC error", 86 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallots, readResponse(rpcerrors)}, blankHandler)), 87 | want{ 88 | true, 89 | "failed to get ballots", 90 | rpc.Ballots{}, 91 | }, 92 | }, 93 | { 94 | "handles failure to unmarshal", 95 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallots, []byte(`junk`)}, blankHandler)), 96 | want{ 97 | true, 98 | "failed to get ballots: failed to parse json", 99 | rpc.Ballots{}, 100 | }, 101 | }, 102 | { 103 | "is successful", 104 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regBallots, readResponse(ballots)}, blankHandler)), 105 | want{ 106 | false, 107 | "", 108 | *goldenBallots, 109 | }, 110 | }, 111 | } 112 | 113 | for _, tt := range cases { 114 | t.Run(tt.name, func(t *testing.T) { 115 | server := httptest.NewServer(tt.inputHanler) 116 | defer server.Close() 117 | 118 | r, err := rpc.New(server.URL) 119 | assert.Nil(t, err) 120 | 121 | _, ballots, err := r.Ballots(&rpc.BlockIDHead{}) 122 | checkErr(t, tt.wantErr, tt.containsErr, err) 123 | assert.Equal(t, tt.want.ballots, ballots) 124 | }) 125 | } 126 | } 127 | 128 | func Test_CurrentPeriod(t *testing.T) { 129 | type want struct { 130 | wantErr bool 131 | containsErr string 132 | currentPeriod rpc.VotingPeriod 133 | } 134 | 135 | cases := []struct { 136 | name string 137 | inputHanler http.Handler 138 | want 139 | }{ 140 | { 141 | "handles RPC error", 142 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriod, readResponse(rpcerrors)}, blankHandler)), 143 | want{ 144 | true, 145 | "failed to get current period", 146 | rpc.VotingPeriod{}, 147 | }, 148 | }, 149 | { 150 | "handles failure to unmarshal", 151 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriod, []byte(`junk`)}, blankHandler)), 152 | want{ 153 | true, 154 | "failed to get current period: failed to parse json", 155 | rpc.VotingPeriod{}, 156 | }, 157 | }, 158 | // TODO: was unable to get real mock data 159 | // { 160 | // "is successful", 161 | // gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriod, []byte(`"promotion_vote"`)}, blankHandler)), 162 | // want{ 163 | // false, 164 | // "", 165 | // "promotion_vote", 166 | // }, 167 | // }, 168 | } 169 | 170 | for _, tt := range cases { 171 | t.Run(tt.name, func(t *testing.T) { 172 | server := httptest.NewServer(tt.inputHanler) 173 | defer server.Close() 174 | 175 | r, err := rpc.New(server.URL) 176 | assert.Nil(t, err) 177 | 178 | _, currentPeriodKind, err := r.CurrentPeriod(&rpc.BlockIDHead{}) 179 | checkErr(t, tt.wantErr, tt.containsErr, err) 180 | assert.Equal(t, tt.want.currentPeriod, currentPeriodKind) 181 | }) 182 | } 183 | } 184 | 185 | func Test_CurrentPeriodKind(t *testing.T) { 186 | type want struct { 187 | wantErr bool 188 | containsErr string 189 | currentPeriodKind string 190 | } 191 | 192 | cases := []struct { 193 | name string 194 | inputHanler http.Handler 195 | want 196 | }{ 197 | { 198 | "handles RPC error", 199 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriodKind, readResponse(rpcerrors)}, blankHandler)), 200 | want{ 201 | true, 202 | "failed to get current period kind", 203 | "", 204 | }, 205 | }, 206 | { 207 | "handles failure to unmarshal", 208 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriodKind, []byte(`junk`)}, blankHandler)), 209 | want{ 210 | true, 211 | "failed to get current period kind: failed to parse json", 212 | "", 213 | }, 214 | }, 215 | { 216 | "is successful", 217 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriodKind, []byte(`"promotion_vote"`)}, blankHandler)), 218 | want{ 219 | false, 220 | "", 221 | "promotion_vote", 222 | }, 223 | }, 224 | } 225 | 226 | for _, tt := range cases { 227 | t.Run(tt.name, func(t *testing.T) { 228 | server := httptest.NewServer(tt.inputHanler) 229 | defer server.Close() 230 | 231 | r, err := rpc.New(server.URL) 232 | assert.Nil(t, err) 233 | 234 | _, currentPeriodKind, err := r.CurrentPeriodKind(&rpc.BlockIDHead{}) 235 | checkErr(t, tt.wantErr, tt.containsErr, err) 236 | assert.Equal(t, tt.want.currentPeriodKind, currentPeriodKind) 237 | }) 238 | } 239 | } 240 | 241 | func Test_CurrentProposal(t *testing.T) { 242 | type want struct { 243 | wantErr bool 244 | containsErr string 245 | currentProposal string 246 | } 247 | 248 | cases := []struct { 249 | name string 250 | inputHanler http.Handler 251 | want 252 | }{ 253 | { 254 | "handles RPC error", 255 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentProposal, readResponse(rpcerrors)}, blankHandler)), 256 | want{ 257 | true, 258 | "failed to get current proposal", 259 | "", 260 | }, 261 | }, 262 | { 263 | "handles failure to unmarshal", 264 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentProposal, []byte(`junk`)}, blankHandler)), 265 | want{ 266 | true, 267 | "failed to get current proposal: failed to parse json", 268 | "", 269 | }, 270 | }, 271 | { 272 | "is successful", 273 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentProposal, []byte(`"PtEdoTezd3RHSC31mpxxo1npxFjoWWcFgQtxapi51Z8TLu6v6Uq"`)}, blankHandler)), 274 | want{ 275 | false, 276 | "", 277 | "PtEdoTezd3RHSC31mpxxo1npxFjoWWcFgQtxapi51Z8TLu6v6Uq", 278 | }, 279 | }, 280 | } 281 | 282 | for _, tt := range cases { 283 | t.Run(tt.name, func(t *testing.T) { 284 | server := httptest.NewServer(tt.inputHanler) 285 | defer server.Close() 286 | 287 | r, err := rpc.New(server.URL) 288 | assert.Nil(t, err) 289 | 290 | _, currentProposal, err := r.CurrentProposal(&rpc.BlockIDHead{}) 291 | checkErr(t, tt.wantErr, tt.containsErr, err) 292 | assert.Equal(t, tt.want.currentProposal, currentProposal) 293 | }) 294 | } 295 | } 296 | 297 | func Test_CurrentQuorum(t *testing.T) { 298 | type want struct { 299 | wantErr bool 300 | containsErr string 301 | currentQuorum int 302 | } 303 | 304 | cases := []struct { 305 | name string 306 | inputHanler http.Handler 307 | want 308 | }{ 309 | { 310 | "handles RPC error", 311 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentQuorum, readResponse(rpcerrors)}, blankHandler)), 312 | want{ 313 | true, 314 | "failed to get current quorum", 315 | 0, 316 | }, 317 | }, 318 | { 319 | "handles failure to unmarshal", 320 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentQuorum, []byte(`junk`)}, blankHandler)), 321 | want{ 322 | true, 323 | "failed to get current quorum: failed to parse json", 324 | 0, 325 | }, 326 | }, 327 | { 328 | "is successful", 329 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentQuorum, []byte(`7470`)}, blankHandler)), 330 | want{ 331 | false, 332 | "", 333 | 7470, 334 | }, 335 | }, 336 | } 337 | 338 | for _, tt := range cases { 339 | t.Run(tt.name, func(t *testing.T) { 340 | server := httptest.NewServer(tt.inputHanler) 341 | defer server.Close() 342 | 343 | r, err := rpc.New(server.URL) 344 | assert.Nil(t, err) 345 | 346 | _, currentQuorum, err := r.CurrentQuorum(&rpc.BlockIDHead{}) 347 | checkErr(t, tt.wantErr, tt.containsErr, err) 348 | assert.Equal(t, tt.want.currentQuorum, currentQuorum) 349 | }) 350 | } 351 | } 352 | 353 | func Test_VoteListings(t *testing.T) { 354 | goldenVoteListings := getResponse(voteListings).(rpc.Listings) 355 | 356 | type want struct { 357 | wantErr bool 358 | containsErr string 359 | voteListings rpc.Listings 360 | } 361 | 362 | cases := []struct { 363 | name string 364 | inputHanler http.Handler 365 | want 366 | }{ 367 | { 368 | "handles RPC error", 369 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regVoteListings, readResponse(rpcerrors)}, blankHandler)), 370 | want{ 371 | true, 372 | "failed to get listings", 373 | rpc.Listings{}, 374 | }, 375 | }, 376 | { 377 | "handles failure to unmarshal", 378 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regVoteListings, []byte(`junk`)}, blankHandler)), 379 | want{ 380 | true, 381 | "failed to get listings: failed to parse json", 382 | rpc.Listings{}, 383 | }, 384 | }, 385 | { 386 | "is successful", 387 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regVoteListings, readResponse(voteListings)}, blankHandler)), 388 | want{ 389 | false, 390 | "", 391 | goldenVoteListings, 392 | }, 393 | }, 394 | } 395 | 396 | for _, tt := range cases { 397 | t.Run(tt.name, func(t *testing.T) { 398 | server := httptest.NewServer(tt.inputHanler) 399 | defer server.Close() 400 | 401 | r, err := rpc.New(server.URL) 402 | assert.Nil(t, err) 403 | 404 | _, voteListings, err := r.Listings(&rpc.BlockIDHead{}) 405 | checkErr(t, tt.wantErr, tt.containsErr, err) 406 | assert.Equal(t, tt.want.voteListings, voteListings) 407 | }) 408 | } 409 | } 410 | 411 | func Test_Proposals(t *testing.T) { 412 | goldenProposals := getResponse(proposals).(rpc.Proposals) 413 | 414 | type want struct { 415 | wantErr bool 416 | containsErr string 417 | proposals rpc.Proposals 418 | } 419 | 420 | cases := []struct { 421 | name string 422 | inputHanler http.Handler 423 | want 424 | }{ 425 | { 426 | "handles RPC error", 427 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regProposals, readResponse(rpcerrors)}, blankHandler)), 428 | want{ 429 | true, 430 | "failed to get proposals", 431 | rpc.Proposals{}, 432 | }, 433 | }, 434 | { 435 | "handles failure to unmarshal", 436 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regProposals, []byte(`junk`)}, blankHandler)), 437 | want{ 438 | true, 439 | "failed to get proposals: failed to parse json", 440 | rpc.Proposals{}, 441 | }, 442 | }, 443 | { 444 | "is successful", 445 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regProposals, readResponse(proposals)}, blankHandler)), 446 | want{ 447 | false, 448 | "", 449 | goldenProposals, 450 | }, 451 | }, 452 | } 453 | 454 | for _, tt := range cases { 455 | t.Run(tt.name, func(t *testing.T) { 456 | server := httptest.NewServer(tt.inputHanler) 457 | defer server.Close() 458 | 459 | r, err := rpc.New(server.URL) 460 | assert.Nil(t, err) 461 | 462 | _, proposals, err := r.Proposals(&rpc.BlockIDHead{}) 463 | checkErr(t, tt.wantErr, tt.containsErr, err) 464 | assert.Equal(t, tt.want.proposals, proposals) 465 | }) 466 | } 467 | } 468 | 469 | func Test_SuccessorPeriod(t *testing.T) { 470 | type want struct { 471 | wantErr bool 472 | containsErr string 473 | successorPeriod rpc.VotingPeriod 474 | } 475 | 476 | cases := []struct { 477 | name string 478 | inputHanler http.Handler 479 | want 480 | }{ 481 | { 482 | "handles RPC error", 483 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regSuccessorPeriod, readResponse(rpcerrors)}, blankHandler)), 484 | want{ 485 | true, 486 | "failed to get successor period", 487 | rpc.VotingPeriod{}, 488 | }, 489 | }, 490 | { 491 | "handles failure to unmarshal", 492 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regSuccessorPeriod, []byte(`junk`)}, blankHandler)), 493 | want{ 494 | true, 495 | "failed to get successor period: failed to parse json", 496 | rpc.VotingPeriod{}, 497 | }, 498 | }, 499 | // TODO: was unable to get real mock data 500 | // { 501 | // "is successful", 502 | // gtGoldenHTTPMock(mockHandler(&requestResultPair{regCurrentPeriod, []byte(`"promotion_vote"`)}, blankHandler)), 503 | // want{ 504 | // false, 505 | // "", 506 | // "promotion_vote", 507 | // }, 508 | // }, 509 | } 510 | 511 | for _, tt := range cases { 512 | t.Run(tt.name, func(t *testing.T) { 513 | server := httptest.NewServer(tt.inputHanler) 514 | defer server.Close() 515 | 516 | r, err := rpc.New(server.URL) 517 | assert.Nil(t, err) 518 | 519 | _, successorPeriod, err := r.SuccessorPeriod(&rpc.BlockIDHead{}) 520 | checkErr(t, tt.wantErr, tt.containsErr, err) 521 | assert.Equal(t, tt.want.successorPeriod, successorPeriod) 522 | }) 523 | } 524 | } 525 | 526 | func Test_TotalVotingPower(t *testing.T) { 527 | type want struct { 528 | wantErr bool 529 | containsErr string 530 | votingPower int 531 | } 532 | 533 | cases := []struct { 534 | name string 535 | inputHanler http.Handler 536 | want 537 | }{ 538 | { 539 | "handles RPC error", 540 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regTotalVotingPower, readResponse(rpcerrors)}, blankHandler)), 541 | want{ 542 | true, 543 | "failed to get total voting power", 544 | 0, 545 | }, 546 | }, 547 | { 548 | "handles failure to unmarshal", 549 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regTotalVotingPower, []byte(`junk`)}, blankHandler)), 550 | want{ 551 | true, 552 | "failed to get total voting power: failed to parse json", 553 | 0, 554 | }, 555 | }, 556 | { 557 | "is successful", 558 | gtGoldenHTTPMock(mockHandler(&requestResultPair{regTotalVotingPower, []byte(`7470`)}, blankHandler)), 559 | want{ 560 | false, 561 | "", 562 | 7470, 563 | }, 564 | }, 565 | } 566 | 567 | for _, tt := range cases { 568 | t.Run(tt.name, func(t *testing.T) { 569 | server := httptest.NewServer(tt.inputHanler) 570 | defer server.Close() 571 | 572 | r, err := rpc.New(server.URL) 573 | assert.Nil(t, err) 574 | 575 | _, votingPower, err := r.TotalVotingPower(&rpc.BlockIDHead{}) 576 | checkErr(t, tt.wantErr, tt.containsErr, err) 577 | assert.Equal(t, tt.want.votingPower, votingPower) 578 | }) 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /rpc/context_integration_test.go: -------------------------------------------------------------------------------- 1 | // +build integration 2 | 3 | package rpc_test 4 | 5 | import ( 6 | "math/rand" 7 | "testing" 8 | 9 | "github.com/goat-systems/go-tezos/v4/forge" 10 | "github.com/goat-systems/go-tezos/v4/rpc" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | const HOST = "https://mainnet-tezos.giganode.io" 15 | 16 | func getRandomBlock(r *rpc.Client, from int, t *testing.T) *rpc.Block { 17 | _, head, err := r.Block(&rpc.BlockIDHead{}) 18 | if ok := assert.Nil(t, err, "Random block generator failed to get current network height"); !ok { 19 | t.FailNow() 20 | } 21 | 22 | id := rpc.BlockIDLevel(rand.Intn((head.Header.Level - from)) + from) 23 | _, block, err := r.Block(&id) 24 | if ok := assert.Nil(t, err, "Random block generator failed to get block"); !ok { 25 | t.FailNow() 26 | } 27 | 28 | return block 29 | } 30 | 31 | func Test_Integration_BigMap(t *testing.T) { 32 | r, err := rpc.New(HOST) 33 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 34 | t.FailNow() 35 | } 36 | 37 | scriptExp, err := forge.ForgeAddressExpression("tz1UKGJ98tjySyWkFFtaPRXFjoYZrzb5rhPD") 38 | if ok := assert.Nil(t, err, "Failed to forge script expression."); !ok { 39 | t.FailNow() 40 | } 41 | 42 | block := getRandomBlock(r, 1208511, t) 43 | 44 | id := rpc.BlockIDHash(block.Hash) 45 | _, err = r.BigMap(rpc.BigMapInput{ 46 | BlockID: &id, 47 | BigMapID: 123, // tzBTC big_map ID 48 | ScriptExpression: scriptExp, 49 | }) 50 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 51 | t.FailNow() 52 | } 53 | } 54 | 55 | func Test_Integration_Constants(t *testing.T) { 56 | r, err := rpc.New(HOST) 57 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 58 | t.FailNow() 59 | } 60 | 61 | for i := 0; i < 5; i++ { 62 | block := getRandomBlock(r, 0, t) 63 | 64 | id := rpc.BlockIDHash(block.Hash) 65 | _, _, err = r.Constants(rpc.ConstantsInput{ 66 | BlockID: &id, 67 | }) 68 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 69 | t.FailNow() 70 | } 71 | } 72 | } 73 | 74 | func Test_Integration_Contracts(t *testing.T) { 75 | r, err := rpc.New(HOST) 76 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 77 | t.FailNow() 78 | } 79 | 80 | block := getRandomBlock(r, 0, t) 81 | 82 | id := rpc.BlockIDHash(block.Hash) 83 | _, _, err = r.Contracts(rpc.ContractsInput{ 84 | BlockID: &id, 85 | }) 86 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 87 | t.FailNow() 88 | } 89 | } 90 | 91 | func Test_Integration_Contract(t *testing.T) { 92 | r, err := rpc.New(HOST) 93 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 94 | t.FailNow() 95 | } 96 | 97 | for i := 0; i < 5; i++ { 98 | block := getRandomBlock(r, 1208511, t) 99 | 100 | id := rpc.BlockIDHash(block.Hash) 101 | _, _, err := r.Contract(rpc.ContractInput{ 102 | BlockID: &id, 103 | ContractID: "tz1ZbSrRrfhU8LYHELWNswx2JcFARXTGKKVk", 104 | }) 105 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 106 | t.FailNow() 107 | } 108 | } 109 | } 110 | 111 | func Test_Integration_ContractBalance(t *testing.T) { 112 | r, err := rpc.New(HOST) 113 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 114 | t.FailNow() 115 | } 116 | 117 | for i := 0; i < 5; i++ { 118 | block := getRandomBlock(r, 1127225, t) 119 | 120 | id := rpc.BlockIDHash(block.Hash) 121 | _, _, err := r.ContractBalance(rpc.ContractBalanceInput{ 122 | BlockID: &id, 123 | ContractID: "tz1ZbSrRrfhU8LYHELWNswx2JcFARXTGKKVk", 124 | }) 125 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 126 | t.FailNow() 127 | } 128 | } 129 | } 130 | 131 | func Test_Integration_ContractCounter(t *testing.T) { 132 | r, err := rpc.New(HOST) 133 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 134 | t.FailNow() 135 | } 136 | 137 | for i := 0; i < 5; i++ { 138 | block := getRandomBlock(r, 1127225, t) 139 | 140 | id := rpc.BlockIDHash(block.Hash) 141 | _, _, err := r.ContractCounter(rpc.ContractCounterInput{ 142 | BlockID: &id, 143 | ContractID: "tz1ZbSrRrfhU8LYHELWNswx2JcFARXTGKKVk", 144 | }) 145 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 146 | t.FailNow() 147 | } 148 | } 149 | } 150 | 151 | func Test_Integration_ContractDelegate(t *testing.T) { 152 | r, err := rpc.New(HOST) 153 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 154 | t.FailNow() 155 | } 156 | 157 | for i := 0; i < 5; i++ { 158 | block := getRandomBlock(r, 1275219, t) 159 | 160 | id := rpc.BlockIDHash(block.Hash) 161 | _, _, err := r.ContractDelegate(rpc.ContractDelegateInput{ 162 | BlockID: &id, 163 | ContractID: "tz1ZbSrRrfhU8LYHELWNswx2JcFARXTGKKVk", 164 | }) 165 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 166 | t.FailNow() 167 | } 168 | } 169 | } 170 | 171 | func Test_Integration_ContractEntrypoints(t *testing.T) { 172 | r, err := rpc.New(HOST) 173 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 174 | t.FailNow() 175 | } 176 | 177 | block := getRandomBlock(r, 1269959, t) 178 | 179 | id := rpc.BlockIDHash(block.Hash) 180 | _, _, err = r.ContractEntrypoints(rpc.ContractEntrypointsInput{ 181 | BlockID: &id, 182 | ContractID: "KT1DrJV8vhkdLEj76h1H9Q4irZDqAkMPo1Qf", 183 | }) 184 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 185 | t.FailNow() 186 | } 187 | } 188 | 189 | func Test_Integration_ContractEntrypoint(t *testing.T) { 190 | r, err := rpc.New(HOST) 191 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 192 | t.FailNow() 193 | } 194 | 195 | block := getRandomBlock(r, 1269959, t) 196 | 197 | id := rpc.BlockIDHash(block.Hash) 198 | _, _, err = r.ContractEntrypoint(rpc.ContractEntrypointInput{ 199 | BlockID: &id, 200 | ContractID: "KT1DrJV8vhkdLEj76h1H9Q4irZDqAkMPo1Qf", 201 | Entrypoint: "tokenToToken", 202 | }) 203 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 204 | t.FailNow() 205 | } 206 | } 207 | 208 | func Test_Integration_ContractManagerKey(t *testing.T) { 209 | r, err := rpc.New(HOST) 210 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 211 | t.FailNow() 212 | } 213 | 214 | block := getRandomBlock(r, 1269959, t) 215 | 216 | id := rpc.BlockIDHash(block.Hash) 217 | _, _, err = r.ContractManagerKey(rpc.ContractManagerKeyInput{ 218 | BlockID: &id, 219 | ContractID: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 220 | }) 221 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 222 | t.FailNow() 223 | } 224 | } 225 | 226 | func Test_Integration_ContractScript(t *testing.T) { 227 | r, err := rpc.New(HOST) 228 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 229 | t.FailNow() 230 | } 231 | 232 | block := getRandomBlock(r, 1269959, t) 233 | 234 | id := rpc.BlockIDHash(block.Hash) 235 | _, err = r.ContractScript(rpc.ContractScriptInput{ 236 | BlockID: &id, 237 | ContractID: "KT1DrJV8vhkdLEj76h1H9Q4irZDqAkMPo1Qf", 238 | }) 239 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 240 | t.FailNow() 241 | } 242 | } 243 | 244 | // TODO: EDO sapling contracts are not readily available 245 | //func Test_Integration_ContractSaplingDiff(t *testing.T) {} 246 | 247 | func Test_Integration_ContractStorage(t *testing.T) { 248 | r, err := rpc.New(HOST) 249 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 250 | t.FailNow() 251 | } 252 | 253 | block := getRandomBlock(r, 1269959, t) 254 | 255 | id := rpc.BlockIDHash(block.Hash) 256 | _, err = r.ContractStorage(rpc.ContractStorageInput{ 257 | BlockID: &id, 258 | ContractID: "KT1DrJV8vhkdLEj76h1H9Q4irZDqAkMPo1Qf", 259 | }) 260 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 261 | t.FailNow() 262 | } 263 | } 264 | 265 | func Test_Integration_Delegates(t *testing.T) { 266 | r, err := rpc.New(HOST) 267 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 268 | t.FailNow() 269 | } 270 | 271 | block := getRandomBlock(r, 0, t) 272 | 273 | id := rpc.BlockIDHash(block.Hash) 274 | _, _, err = r.Delegates(rpc.DelegatesInput{ 275 | BlockID: &id, 276 | }) 277 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 278 | t.FailNow() 279 | } 280 | } 281 | 282 | func Test_Integration_Delegate(t *testing.T) { 283 | r, err := rpc.New(HOST) 284 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 285 | t.FailNow() 286 | } 287 | 288 | block := getRandomBlock(r, 1000000, t) 289 | 290 | id := rpc.BlockIDHash(block.Hash) 291 | _, _, err = r.Delegate(rpc.DelegateInput{ 292 | BlockID: &id, 293 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 294 | }) 295 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 296 | t.FailNow() 297 | } 298 | } 299 | 300 | func Test_Integration_DelegateBalance(t *testing.T) { 301 | r, err := rpc.New(HOST) 302 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 303 | t.FailNow() 304 | } 305 | 306 | block := getRandomBlock(r, 1000000, t) 307 | 308 | id := rpc.BlockIDHash(block.Hash) 309 | _, _, err = r.DelegateBalance(rpc.DelegateBalanceInput{ 310 | BlockID: &id, 311 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 312 | }) 313 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 314 | t.FailNow() 315 | } 316 | } 317 | 318 | func Test_Integration_DelegateDeactivated(t *testing.T) { 319 | r, err := rpc.New(HOST) 320 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 321 | t.FailNow() 322 | } 323 | 324 | block := getRandomBlock(r, 1000000, t) 325 | 326 | id := rpc.BlockIDHash(block.Hash) 327 | _, _, err = r.DelegateDeactivated(rpc.DelegateDeactivatedInput{ 328 | BlockID: &id, 329 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 330 | }) 331 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 332 | t.FailNow() 333 | } 334 | } 335 | 336 | func Test_Integration_DelegateDelegatedBalance(t *testing.T) { 337 | r, err := rpc.New(HOST) 338 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 339 | t.FailNow() 340 | } 341 | 342 | block := getRandomBlock(r, 1000000, t) 343 | 344 | id := rpc.BlockIDHash(block.Hash) 345 | _, _, err = r.DelegateDelegatedBalance(rpc.DelegateDelegatedBalanceInput{ 346 | BlockID: &id, 347 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 348 | }) 349 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 350 | t.FailNow() 351 | } 352 | } 353 | 354 | func Test_Integration_DelegateDelegatedContracts(t *testing.T) { 355 | r, err := rpc.New(HOST) 356 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 357 | t.FailNow() 358 | } 359 | 360 | block := getRandomBlock(r, 1000000, t) 361 | 362 | id := rpc.BlockIDHash(block.Hash) 363 | _, _, err = r.DelegateDelegatedContracts(rpc.DelegateDelegatedContractsInput{ 364 | BlockID: &id, 365 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 366 | }) 367 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 368 | t.FailNow() 369 | } 370 | } 371 | 372 | func Test_Integration_DelegateFrozenBalance(t *testing.T) { 373 | r, err := rpc.New(HOST) 374 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 375 | t.FailNow() 376 | } 377 | 378 | block := getRandomBlock(r, 1000000, t) 379 | 380 | id := rpc.BlockIDHash(block.Hash) 381 | _, _, err = r.DelegateFrozenBalance(rpc.DelegateFrozenBalanceInput{ 382 | BlockID: &id, 383 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 384 | }) 385 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 386 | t.FailNow() 387 | } 388 | } 389 | 390 | func Test_Integration_DelegateFrozenBalanceByCycle(t *testing.T) { 391 | r, err := rpc.New(HOST) 392 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 393 | t.FailNow() 394 | } 395 | 396 | block := getRandomBlock(r, 1000000, t) 397 | 398 | id := rpc.BlockIDHash(block.Hash) 399 | _, _, err = r.DelegateFrozenBalanceByCycle(rpc.DelegateFrozenBalanceByCycleInput{ 400 | BlockID: &id, 401 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 402 | }) 403 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 404 | t.FailNow() 405 | } 406 | } 407 | 408 | func Test_Integration_DelegateGracePeriod(t *testing.T) { 409 | r, err := rpc.New(HOST) 410 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 411 | t.FailNow() 412 | } 413 | 414 | block := getRandomBlock(r, 1000000, t) 415 | 416 | id := rpc.BlockIDHash(block.Hash) 417 | _, _, err = r.DelegateGracePeriod(rpc.DelegateGracePeriodInput{ 418 | BlockID: &id, 419 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 420 | }) 421 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 422 | t.FailNow() 423 | } 424 | } 425 | 426 | func Test_Integration_DelegateStakingBalance(t *testing.T) { 427 | r, err := rpc.New(HOST) 428 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 429 | t.FailNow() 430 | } 431 | 432 | block := getRandomBlock(r, 1000000, t) 433 | 434 | id := rpc.BlockIDHash(block.Hash) 435 | _, _, err = r.DelegateStakingBalance(rpc.DelegateStakingBalanceInput{ 436 | BlockID: &id, 437 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 438 | }) 439 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 440 | t.FailNow() 441 | } 442 | } 443 | 444 | func Test_Integration_DelegateVotingPower(t *testing.T) { 445 | r, err := rpc.New(HOST) 446 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 447 | t.FailNow() 448 | } 449 | 450 | block := getRandomBlock(r, 1000000, t) 451 | 452 | id := rpc.BlockIDHash(block.Hash) 453 | _, _, err = r.DelegateVotingPower(rpc.DelegateVotingPowerInput{ 454 | BlockID: &id, 455 | Delegate: "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc", 456 | }) 457 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 458 | t.FailNow() 459 | } 460 | } 461 | 462 | func Test_Integration_Nonces(t *testing.T) { 463 | r, err := rpc.New(HOST) 464 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 465 | t.FailNow() 466 | } 467 | 468 | block := getRandomBlock(r, 1000000, t) 469 | 470 | id := rpc.BlockIDHash(block.Hash) 471 | _, _, err = r.Nonces(rpc.NoncesInput{ 472 | BlockID: &id, 473 | Level: block.Header.Level - 1, 474 | }) 475 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 476 | t.FailNow() 477 | } 478 | } 479 | 480 | func Test_Integration_RawBytes(t *testing.T) { 481 | r, err := rpc.New(HOST) 482 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 483 | t.FailNow() 484 | } 485 | 486 | block := getRandomBlock(r, 1000000, t) 487 | 488 | id := rpc.BlockIDHash(block.Hash) 489 | _, err = r.RawBytes(rpc.RawBytesInput{ 490 | BlockID: &id, 491 | Depth: 1, 492 | }) 493 | if ok := assert.Nilf(t, err, "Failed at block '%d'", block.Header.Level); !ok { 494 | t.FailNow() 495 | } 496 | } 497 | 498 | // TODO: EDO sapling contracts are not readily available 499 | // func Test_SaplingDiff(t *testing.T) {} 500 | 501 | func Test_Integration_Seed(t *testing.T) { 502 | r, err := rpc.New(HOST) 503 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 504 | t.FailNow() 505 | } 506 | 507 | _, head, err := r.Block(&rpc.BlockIDHead{}) 508 | if ok := assert.Nil(t, err, "Failed to generate RPC client."); !ok { 509 | t.FailNow() 510 | } 511 | 512 | id := rpc.BlockIDHash(head.Hash) 513 | _, _, err = r.Seed(rpc.SeedInput{ 514 | BlockID: &id, 515 | }) 516 | if ok := assert.Nilf(t, err, "Failed at block '%d'", head.Header.Level); !ok { 517 | t.FailNow() 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /rpc/mocks_test.go: -------------------------------------------------------------------------------- 1 | package rpc_test 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "net/http" 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/goat-systems/go-tezos/v4/rpc" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | type responseKey string 15 | 16 | func (r *responseKey) String() string { 17 | return string(*r) 18 | } 19 | 20 | const ( 21 | activechains responseKey = ".test-fixtures/active_chains.json" 22 | bakingrights responseKey = ".test-fixtures/baking_rights.json" 23 | balance responseKey = ".test-fixtures/balance.json" 24 | ballotList responseKey = ".test-fixtures/ballot_list.json" 25 | ballots responseKey = ".test-fixtures/ballots.json" 26 | block responseKey = ".test-fixtures/block.json" 27 | blocks responseKey = ".test-fixtures/blocks.json" 28 | chainid responseKey = ".test-fixtures/chain_id.json" 29 | contract responseKey = ".test-fixtures/contract.json" 30 | connections responseKey = ".test-fixtures/connections.json" 31 | constants responseKey = ".test-fixtures/constants.json" 32 | contractEntrypoints responseKey = ".test-fixtures/entrypoints.json" 33 | counter responseKey = ".test-fixtures/counter.json" 34 | currentLevel responseKey = ".test-fixtures/current_level.json" 35 | cycle responseKey = ".test-fixtures/cycle.json" 36 | delegate responseKey = ".test-fixtures/delegate.json" 37 | delegatedcontracts responseKey = ".test-fixtures/delegated_contracts.json" 38 | endorsingrights responseKey = ".test-fixtures/endorsing_rights.json" 39 | frozenbalance responseKey = ".test-fixtures/frozen_balance.json" 40 | frozenbalanceByCycle responseKey = ".test-fixtures/frozen_balance_by_cycle.json" 41 | header responseKey = ".test-fixtures/header.json" 42 | headerShell responseKey = ".test-fixtures/header_shell.json" 43 | liveBlocks responseKey = ".test-fixtures/live_blocks.json" 44 | metadata responseKey = ".test-fixtures/metadata.json" 45 | operations responseKey = ".test-fixtures/operations.json" 46 | operationhashes responseKey = ".test-fixtures/operation_hashes.json" 47 | operationMetaDataHashes responseKey = ".test-fixtures/operation_metadata_hashes.json" 48 | parseOperations responseKey = ".test-fixtures/parse_operations.json" 49 | preapplyOperations responseKey = ".test-fixtures/preapply_operations.json" 50 | proposals responseKey = ".test-fixtures/proposals.json" 51 | protocols responseKey = ".test-fixtures/protocols.json" 52 | protocolData responseKey = ".test-fixtures/protocol_data.json" 53 | rpcerrors responseKey = ".test-fixtures/rpc_errors.json" 54 | voteListings responseKey = ".test-fixtures/vote_listings.json" 55 | ) 56 | 57 | func readResponse(key responseKey) []byte { 58 | f, _ := ioutil.ReadFile(key.String()) 59 | return f 60 | } 61 | 62 | func getResponse(key responseKey) interface{} { 63 | switch key { 64 | case activechains: 65 | f := readResponse(key) 66 | var out rpc.ActiveChains 67 | json.Unmarshal(f, &out) 68 | return out 69 | case bakingrights: 70 | f := readResponse(key) 71 | var out []rpc.BakingRights 72 | json.Unmarshal(f, &out) 73 | return out 74 | case balance: 75 | f := readResponse(key) 76 | var out int 77 | json.Unmarshal(f, &out) 78 | return &out 79 | case ballotList: 80 | f := readResponse(key) 81 | var out rpc.BallotList 82 | json.Unmarshal(f, &out) 83 | return &out 84 | case ballots: 85 | f := readResponse(key) 86 | var out rpc.Ballots 87 | json.Unmarshal(f, &out) 88 | return &out 89 | case block: 90 | f := readResponse(key) 91 | var out rpc.Block 92 | json.Unmarshal(f, &out) 93 | return &out 94 | case blocks: 95 | f := readResponse(key) 96 | var out [][]string 97 | json.Unmarshal(f, &out) 98 | return out 99 | case chainid: 100 | f := readResponse(key) 101 | var out string 102 | json.Unmarshal(f, &out) 103 | return out 104 | case contract: 105 | f := readResponse(key) 106 | var out rpc.Contract 107 | json.Unmarshal(f, &out) 108 | return out 109 | case connections: 110 | f := readResponse(key) 111 | var out rpc.Connections 112 | json.Unmarshal(f, &out) 113 | return out 114 | case constants: 115 | f := readResponse(key) 116 | var out rpc.Constants 117 | json.Unmarshal(f, &out) 118 | return out 119 | case counter: 120 | f := readResponse(key) 121 | var out int 122 | json.Unmarshal(f, &out) 123 | return out 124 | case currentLevel: 125 | f := readResponse(key) 126 | var out rpc.CurrentLevel 127 | json.Unmarshal(f, &out) 128 | return out 129 | case cycle: 130 | f := readResponse(key) 131 | var out rpc.Cycle 132 | json.Unmarshal(f, &out) 133 | return out 134 | case delegate: 135 | f := readResponse(key) 136 | var out rpc.Delegate 137 | json.Unmarshal(f, &out) 138 | return out 139 | case delegatedcontracts: 140 | f := readResponse(key) 141 | var out []string 142 | json.Unmarshal(f, &out) 143 | return out 144 | case endorsingrights: 145 | f := readResponse(key) 146 | var out []rpc.EndorsingRights 147 | json.Unmarshal(f, &out) 148 | return out 149 | case frozenbalanceByCycle: 150 | f := readResponse(key) 151 | var out []rpc.FrozenBalanceByCycle 152 | json.Unmarshal(f, &out) 153 | return out 154 | case header: 155 | f := readResponse(key) 156 | var out rpc.Header 157 | json.Unmarshal(f, &out) 158 | return out 159 | case headerShell: 160 | f := readResponse(key) 161 | var out rpc.HeaderShell 162 | json.Unmarshal(f, &out) 163 | return out 164 | case liveBlocks: 165 | f := readResponse(key) 166 | var out []string 167 | json.Unmarshal(f, &out) 168 | return out 169 | case metadata: 170 | f := readResponse(key) 171 | var out rpc.Metadata 172 | json.Unmarshal(f, &out) 173 | return out 174 | case operations: 175 | f := readResponse(key) 176 | var out rpc.FlattenedOperations 177 | json.Unmarshal(f, &out) 178 | return out 179 | case operationhashes: 180 | f := readResponse(key) 181 | var out rpc.OperationHashes 182 | json.Unmarshal(f, &out) 183 | return out 184 | case operationMetaDataHashes: 185 | f := readResponse(key) 186 | var out rpc.OperationMetadataHashes 187 | json.Unmarshal(f, &out) 188 | return out 189 | case parseOperations: 190 | f := readResponse(key) 191 | var out []rpc.Operations 192 | json.Unmarshal(f, &out) 193 | return out 194 | case preapplyOperations: 195 | f := readResponse(key) 196 | var out []rpc.Operations 197 | json.Unmarshal(f, &out) 198 | return out 199 | case proposals: 200 | f := readResponse(key) 201 | var out rpc.Proposals 202 | json.Unmarshal(f, &out) 203 | return out 204 | case protocols: 205 | f := readResponse(key) 206 | var out rpc.Protocols 207 | json.Unmarshal(f, &out) 208 | return out 209 | case protocolData: 210 | f := readResponse(key) 211 | var out rpc.ProtocolData 212 | json.Unmarshal(f, &out) 213 | return out 214 | case rpcerrors: 215 | f := readResponse(key) 216 | var out rpc.Errors 217 | json.Unmarshal(f, &out) 218 | return out 219 | case voteListings: 220 | f := readResponse(key) 221 | var out rpc.Listings 222 | json.Unmarshal(f, &out) 223 | return out 224 | default: 225 | return nil 226 | } 227 | } 228 | 229 | // The below variables contain mocks that are unmarshaled. 230 | var ( 231 | mockAddressTz1 = "tz1SUgyRB8T5jXgXAwS33pgRHAKrafyg87Yc" 232 | mockBlockHash = "BLzGD63HA4RP8Fh5xEtvdQSMKa2WzJMZjQPNVUc4Rqy8Lh5BEY1" 233 | ) 234 | 235 | // Regexes to allow the capture of custom handlers for unit testing. 236 | var ( 237 | regActiveChains = regexp.MustCompile(`\/monitor\/active_chains`) 238 | regBakingRights = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/baking_rights`) 239 | regContractBalance = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/balance`) 240 | regBallotList = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/ballot_list`) 241 | regBallots = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/ballots`) 242 | regBlock = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+`) 243 | regBigMap = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/big_maps\/[0-9]+\/[A-z0-9]+`) 244 | regContracts = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts`) 245 | regContract = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+`) 246 | regConnections = regexp.MustCompile(`\/network\/connections`) 247 | regConstants = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/constants`) 248 | regContractDelegate = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/delegate`) 249 | regContractEntrypoints = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/entrypoints`) 250 | regContractEntrypoint = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/entrypoints\/[A-z]+`) 251 | regContractManagerKey = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/manager_key`) 252 | regContractScript = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/script`) 253 | regContractCounter = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/counter`) 254 | regCurrentPeriodKind = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/current_period_kind`) 255 | regCurrentPeriod = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/current_period_kind`) 256 | regSuccessorPeriod = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/successor_period`) 257 | regCurrentLevel = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/current_level`) 258 | regCurrentProposal = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/current_proposal`) 259 | regCurrentQuorum = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/current_quorum`) 260 | regTotalVotingPower = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/total_voting_power`) 261 | regCycle = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/raw\/json\/cycle\/[0-9]+`) 262 | regDelegate = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+`) 263 | regDelegates = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates`) 264 | regDelegateDelegatedContracts = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/delegated_contracts`) 265 | regDelegateBalance = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/balance`) 266 | regDelegateDeactivated = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/deactivated`) 267 | regDelegateDelegatedBalance = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/delegated_balance`) 268 | regDelegateFrozenBalance = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/frozen_balance`) 269 | regDelegateFrozenBalanceByCycle = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/frozen_balance_by_cycle`) 270 | regDelegateGracePeriod = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/grace_period`) 271 | regDelegateVotingPower = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/voting_power`) 272 | regEndorsingRights = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/endorsing_rights`) 273 | regEndorsingPower = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/endorsing_power`) 274 | regEntrypoint = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers/scripts/entrypoint`) 275 | regEntrypoints = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers/scripts/entrypoints`) 276 | regForgeOperationWithRPC = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/forge\/operations`) 277 | regForgeBlockHeader = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/forge_block_header`) 278 | regHash = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/hash`) 279 | regHeader = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/header`) 280 | regHeaderRaw = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/header\/raw`) 281 | regHeaderShell = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/header\/shell`) 282 | regHeaderProtocolData = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/header\/protocol_data`) 283 | regHeaderProtocolDataRaw = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/header\/protocol_data/raw`) 284 | regLiveBlocks = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/live_blocks`) 285 | regInjectionBlock = regexp.MustCompile(`\/injection\/block`) 286 | regInjectionOperation = regexp.MustCompile(`\/injection\/operation`) 287 | regLevelsInCurrentCycle = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/levels_in_current_cycle`) 288 | regMetadata = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/metadata`) 289 | regMetadataHash = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/metadata_hash`) 290 | regMinimalValidTime = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/minimal_valid_time`) 291 | regNonces = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/nonces\/[0-9]+`) 292 | regOperations = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/operations`) 293 | regOperationsMetadataHash = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/operations_metadata_hash`) 294 | regOperationHashes = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/operation_hashes`) 295 | regOperationMetadataHashes = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/operation_metadata_hashes`) 296 | regPackData = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers/scripts/pack_data`) 297 | regParseBlock = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/parse\/block`) 298 | regParseOperations = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/parse\/operations`) 299 | regPreapplyBlock = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/preapply\/block`) 300 | regPreapplyOperations = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/preapply\/operations`) 301 | regProposals = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/proposals`) 302 | regProtocols = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/protocols`) 303 | regRawBytes = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/raw\/bytes`) 304 | regRunCode = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers/scripts/run_code`) 305 | regRunOperation = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/scripts\/run_operation`) 306 | regRequiredEndorsements = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/required_endorsements`) 307 | regSeed = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/seed`) 308 | regTraceCode = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/scripts\/trace_code`) 309 | regTypecheckCode = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/scripts\/typecheck_code`) 310 | regTypecheckData = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/helpers\/scripts\/typecheck_data`) 311 | regDelegateStakingBalance = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/delegates\/[A-z0-9]+\/staking_balance`) 312 | regContractStorage = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/context\/contracts\/[A-z0-9]+\/storage`) 313 | regVoteListings = regexp.MustCompile(`\/chains\/main\/blocks\/[A-z0-9]+\/votes\/listings`) 314 | ) 315 | 316 | // blankHandler handles the end of a http test handler chain 317 | var blankHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) 318 | 319 | // ----------------------------------------- // 320 | // Mock Handlers 321 | // The below handlers are to simulate the Tezos RPC server for unit testing. 322 | // ----------------------------------------- // 323 | 324 | type requestResultPair struct { 325 | requestPath *regexp.Regexp 326 | resp []byte 327 | } 328 | 329 | func mockHandler(pair *requestResultPair, next http.Handler) http.Handler { 330 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 331 | if pair.requestPath.MatchString(r.URL.String()) { 332 | w.Write(pair.resp) 333 | return 334 | } 335 | 336 | next.ServeHTTP(w, r) 337 | }) 338 | } 339 | 340 | type blockHandlerMock struct { 341 | used bool 342 | } 343 | 344 | func newBlockMock() *blockHandlerMock { 345 | return &blockHandlerMock{} 346 | } 347 | 348 | func (b *blockHandlerMock) handler(resp []byte, next http.Handler) http.Handler { 349 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 350 | if regBlock.MatchString(r.URL.String()) && !b.used { 351 | w.Write(resp) 352 | b.used = true 353 | return 354 | } 355 | 356 | next.ServeHTTP(w, r) 357 | }) 358 | } 359 | 360 | type constantsHandlerMock struct { 361 | used bool 362 | } 363 | 364 | func newConstantsMock() *constantsHandlerMock { 365 | return &constantsHandlerMock{} 366 | } 367 | 368 | func (c *constantsHandlerMock) handler(resp []byte, next http.Handler) http.Handler { 369 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 370 | if regConstants.MatchString(r.URL.String()) && !c.used { 371 | w.Write(resp) 372 | c.used = true 373 | return 374 | } 375 | 376 | next.ServeHTTP(w, r) 377 | }) 378 | } 379 | 380 | func cycleHandlerMock(resp []byte, next http.Handler) http.Handler { 381 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 382 | if regCycle.MatchString(r.URL.String()) { 383 | w.Write(resp) 384 | return 385 | } 386 | 387 | next.ServeHTTP(w, r) 388 | }) 389 | } 390 | 391 | func runOperationHandlerMock(resp []byte, next http.Handler) http.Handler { 392 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 393 | if regRunOperation.MatchString(r.URL.String()) { 394 | w.Write(resp) 395 | return 396 | } 397 | 398 | next.ServeHTTP(w, r) 399 | }) 400 | } 401 | 402 | func checkErr(t *testing.T, wantErr bool, errContains string, err error) { 403 | if wantErr { 404 | assert.Error(t, err) 405 | if err != nil { 406 | assert.Contains(t, err.Error(), errContains) 407 | } 408 | } else { 409 | assert.Nil(t, err) 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /forge/forge_test.go: -------------------------------------------------------------------------------- 1 | package forge 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/goat-systems/go-tezos/v4/internal/testutils" 9 | "github.com/goat-systems/go-tezos/v4/rpc" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func Test_ForgeOperation(t *testing.T) { 14 | transactionJSON := []byte(`{ 15 | "kind": "transaction", 16 | "source": "tz1XJ1UNechmHKhQo4tvVX6qztnVuQuSFKgd", 17 | "fee": "1283", 18 | "counter": "7", 19 | "gas_limit": "10307", 20 | "storage_limit": "0", 21 | "amount": "20000000000", 22 | "destination": "tz1aWXP237BLwNHJcCD4b3DutCevhqq2T1Z9" 23 | }`) 24 | 25 | var transaction rpc.Content 26 | err := json.Unmarshal(transactionJSON, &transaction) 27 | testutils.CheckErr(t, false, "", err) 28 | 29 | transactionJSON2 := []byte(`{ 30 | "kind": "transaction", 31 | "source": "tz1SJJY253HoEda8PS5vvfHVtyghgK3CTS2z", 32 | "fee": "2966", 33 | "counter": "133558", 34 | "gas_limit": "26271", 35 | "storage_limit": "0", 36 | "amount": "0", 37 | "destination": "KT1XdCkJncWfGvqf1NdbK2HBRTvRcHhJtNx5", 38 | "parameters": { 39 | "entrypoint": "do", 40 | "value": [ 41 | { 42 | "prim": "RENAME" 43 | }, 44 | { 45 | "prim": "NIL", 46 | "args": [ 47 | { 48 | "prim": "operation" 49 | } 50 | ] 51 | }, 52 | { 53 | "prim": "PUSH", 54 | "args": [ 55 | { 56 | "prim": "key_hash" 57 | }, 58 | { 59 | "string": "tz2L2HuhaaSnf6ShEDdhTEAr5jGPWPNwpvcB" 60 | } 61 | ] 62 | }, 63 | { 64 | "prim": "IMPLICIT_ACCOUNT" 65 | }, 66 | { 67 | "prim": "PUSH", 68 | "args": [ 69 | { 70 | "prim": "mutez" 71 | }, 72 | { 73 | "int": "2" 74 | } 75 | ] 76 | }, 77 | { 78 | "prim": "UNIT" 79 | }, 80 | { 81 | "prim": "TRANSFER_TOKENS" 82 | }, 83 | { 84 | "prim": "CONS" 85 | }, 86 | { 87 | "prim": "DIP", 88 | "args": [ 89 | [ 90 | { 91 | "prim": "DROP" 92 | } 93 | ] 94 | ] 95 | } 96 | ] 97 | } 98 | }`) 99 | 100 | var transaction2 rpc.Content 101 | err = json.Unmarshal(transactionJSON2, &transaction2) 102 | testutils.CheckErr(t, false, "", err) 103 | 104 | transactionJSON3 := []byte(`{ 105 | "kind": "transaction", 106 | "source": "tz1f2MeahW6XMLcfHJSU5VH8USC4EuFiwdhx", 107 | "fee": "1188", 108 | "counter": "6", 109 | "gas_limit": "10307", 110 | "storage_limit": "0", 111 | "amount": "50000000000", 112 | "destination": "tz1aWXP237BLwNHJcCD4b3DutCevhqq2T1Z9" 113 | }`) 114 | 115 | var transaction3 rpc.Content 116 | err = json.Unmarshal(transactionJSON3, &transaction3) 117 | testutils.CheckErr(t, false, "", err) 118 | 119 | revealJSON := []byte(`{ 120 | "kind": "reveal", 121 | "source": "tz1f2MeahW6XMLcfHJSU5VH8USC4EuFiwdhx", 122 | "fee": "1257", 123 | "counter": "5", 124 | "gas_limit": "10000", 125 | "storage_limit": "0", 126 | "public_key": "edpkuEmaQSYKgDj5k9wfE3bTxjfjoG9k5YvRmYZsGf2bjEymZKkzNn" 127 | }`) 128 | 129 | var reveal rpc.Content 130 | err = json.Unmarshal(revealJSON, &reveal) 131 | testutils.CheckErr(t, false, "", err) 132 | 133 | type input struct { 134 | branch string 135 | contents []rpc.Content 136 | } 137 | 138 | type want struct { 139 | err bool 140 | errContains string 141 | operation string 142 | } 143 | 144 | cases := []struct { 145 | name string 146 | input input 147 | want want 148 | }{ 149 | { 150 | "is successful with transaction", 151 | input{ 152 | branch: "BLQMkH2PSTuAJgVm6rGHshY5z6Z6SAmqXv6q1LDzhX6fchJ12Up", 153 | contents: []rpc.Content{ 154 | transaction, 155 | }, 156 | }, 157 | want{ 158 | false, 159 | "", 160 | "5aff622d53d32a8bae591627718c60a35b16737e301c57a13b6f1765483d88ff6c007fd82c06cf5a203f18faaf562447ed1efcc6c010830a07c350008090dfc04a0000a31e81ac3425310e3274a4698a793b2839dc0afa00", 161 | }, 162 | }, 163 | { 164 | "is successful with transaction 2", 165 | input{ 166 | branch: "BLEkC1TqtP7DJjGnyxwhT8VDnEF75aNMMKS5qJXSTFmAKkV7Pch", 167 | contents: []rpc.Content{ 168 | transaction2, 169 | }, 170 | }, 171 | want{ 172 | false, 173 | "", 174 | "452b8599b0e4960b884d3ad61c89c594bc3348c798842651d3fa6cfafa77ce556c00490dc9520ec45270f240a3cc4f07aec76adc358d9617b693089fcd01000001fcc0bee1480bfca3a80481904cee4099400b1c8d00ff020000004f020000004a0358053d036d0743035d0100000024747a324c324875686161536e663653684544646854454172356a475057504e7770766342031e0743036a0002034f034d031b051f02000000020320", 175 | }, 176 | }, 177 | { 178 | "is successful with multiple transactions", 179 | input{ 180 | branch: "BLEkC1TqtP7DJjGnyxwhT8VDnEF75aNMMKS5qJXSTFmAKkV7Pch", 181 | contents: []rpc.Content{ 182 | transaction, 183 | transaction2, 184 | }, 185 | }, 186 | want{ 187 | false, 188 | "", 189 | "452b8599b0e4960b884d3ad61c89c594bc3348c798842651d3fa6cfafa77ce556c007fd82c06cf5a203f18faaf562447ed1efcc6c010830a07c350008090dfc04a0000a31e81ac3425310e3274a4698a793b2839dc0afa006c00490dc9520ec45270f240a3cc4f07aec76adc358d9617b693089fcd01000001fcc0bee1480bfca3a80481904cee4099400b1c8d00ff020000004f020000004a0358053d036d0743035d0100000024747a324c324875686161536e663653684544646854454172356a475057504e7770766342031e0743036a0002034f034d031b051f02000000020320", 190 | }, 191 | }, 192 | { 193 | "is successful with reveal and transaction", 194 | input{ 195 | branch: "BLCFdxw2kWJfCk9TWQsYxrQd9CcPPs2YdbArbDDgL4GZTYvTfZN", 196 | contents: []rpc.Content{ 197 | reveal, 198 | transaction3, 199 | }, 200 | }, 201 | want{ 202 | false, 203 | "", 204 | "3f82cf0634a5965032d087daa63cf3603dd0f0325e2d670fee91b20486caa0d36b00d4a35d6c49ffbaa32b40e96c844dc485b0cdb5fae90905904e00004e7097e206a9afa864475095b58009014f9c24efd54c5d40240c1e807b4ab80c6c00d4a35d6c49ffbaa32b40e96c844dc485b0cdb5faa40906c3500080e8eda1ba010000a31e81ac3425310e3274a4698a793b2839dc0afa00", 205 | }, 206 | }, 207 | } 208 | 209 | for _, tt := range cases { 210 | t.Run(tt.name, func(t *testing.T) { 211 | operation, err := Encode(tt.input.branch, tt.input.contents...) 212 | testutils.CheckErr(t, tt.want.err, tt.want.errContains, err) 213 | assert.Equal(t, tt.want.operation, operation) 214 | }) 215 | } 216 | } 217 | 218 | func Test_Forge_Origination(t *testing.T) { 219 | originationJSON := []byte(`{ 220 | "kind": "origination", 221 | "source": "tz1TJCwoX79reCZ8yccPeW8iB9Mba91v8H47", 222 | "fee": "1389", 223 | "counter": "307028", 224 | "gas_limit": "11140", 225 | "storage_limit": "323", 226 | "balance": "0", 227 | "script": { 228 | "code": [ 229 | { 230 | "prim": "parameter", 231 | "args": [ 232 | { 233 | "prim": "unit", 234 | "annots": [ 235 | "%abc" 236 | ] 237 | } 238 | ] 239 | }, 240 | { 241 | "prim": "storage", 242 | "args": [ 243 | { 244 | "prim": "unit" 245 | } 246 | ] 247 | }, 248 | { 249 | "prim": "code", 250 | "args": [ 251 | [ 252 | { 253 | "prim": "CDR" 254 | }, 255 | { 256 | "prim": "NIL", 257 | "args": [ 258 | { 259 | "prim": "operation" 260 | } 261 | ] 262 | }, 263 | { 264 | "prim": "PAIR" 265 | } 266 | ] 267 | ] 268 | } 269 | ], 270 | "storage": { 271 | "prim": "Unit" 272 | } 273 | } 274 | }`) 275 | 276 | var origination rpc.Origination 277 | err := json.Unmarshal(originationJSON, &origination) 278 | testutils.CheckErr(t, false, "", err) 279 | 280 | originationJSON2 := []byte(`{ 281 | "kind": "origination", 282 | "source": "tz1TJCwoX79reCZ8yccPeW8iB9Mba91v8H47", 283 | "fee": "2070", 284 | "counter": "307027", 285 | "gas_limit": "15919", 286 | "storage_limit": "526", 287 | "balance": "0", 288 | "script": { 289 | "code": [ 290 | { 291 | "prim": "parameter", 292 | "args": [ 293 | { 294 | "prim": "unit" 295 | } 296 | ] 297 | }, 298 | { 299 | "prim": "storage", 300 | "args": [ 301 | { 302 | "prim": "unit" 303 | } 304 | ] 305 | }, 306 | { 307 | "prim": "code", 308 | "args": [ 309 | [ 310 | { 311 | "prim": "DUP" 312 | }, 313 | { 314 | "prim": "DIP", 315 | "args": [ 316 | [ 317 | { 318 | "prim": "CDR" 319 | } 320 | ] 321 | ] 322 | }, 323 | { 324 | "prim": "CAR" 325 | }, 326 | { 327 | "prim": "PUSH", 328 | "args": [ 329 | { 330 | "prim": "address" 331 | }, 332 | { 333 | "string": "KT1M8MStwA1R5SuGx2V6AMvgd8dGAFYcEUmu" 334 | } 335 | ] 336 | }, 337 | { 338 | "prim": "CONTRACT", 339 | "args": [ 340 | { 341 | "prim": "or", 342 | "args": [ 343 | { 344 | "prim": "lambda", 345 | "args": [ 346 | { 347 | "prim": "unit" 348 | }, 349 | { 350 | "prim": "list", 351 | "args": [ 352 | { 353 | "prim": "operation" 354 | } 355 | ] 356 | } 357 | ], 358 | "annots": [ 359 | "%do" 360 | ] 361 | }, 362 | { 363 | "prim": "unit", 364 | "annots": [ 365 | "%default" 366 | ] 367 | } 368 | ] 369 | } 370 | ] 371 | }, 372 | { 373 | "prim": "IF_NONE", 374 | "args": [ 375 | [ 376 | { 377 | "prim": "PUSH", 378 | "args": [ 379 | { 380 | "prim": "string" 381 | }, 382 | { 383 | "string": "type mismatch" 384 | } 385 | ] 386 | }, 387 | { 388 | "prim": "FAILWITH" 389 | } 390 | ], 391 | [ 392 | [ 393 | { 394 | "prim": "DIP", 395 | "args": [ 396 | { 397 | "int": "2" 398 | }, 399 | [ 400 | { 401 | "prim": "DUP" 402 | } 403 | ] 404 | ] 405 | }, 406 | { 407 | "prim": "DIG", 408 | "args": [ 409 | { 410 | "int": "3" 411 | } 412 | ] 413 | } 414 | ], 415 | { 416 | "prim": "NIL", 417 | "args": [ 418 | { 419 | "prim": "operation" 420 | } 421 | ] 422 | }, 423 | [ 424 | { 425 | "prim": "DIP", 426 | "args": [ 427 | { 428 | "int": "2" 429 | }, 430 | [ 431 | { 432 | "prim": "DUP" 433 | } 434 | ] 435 | ] 436 | }, 437 | { 438 | "prim": "DIG", 439 | "args": [ 440 | { 441 | "int": "3" 442 | } 443 | ] 444 | } 445 | ], 446 | { 447 | "prim": "DIP", 448 | "args": [ 449 | { 450 | "int": "3" 451 | }, 452 | [ 453 | { 454 | "prim": "DROP" 455 | } 456 | ] 457 | ] 458 | }, 459 | { 460 | "prim": "PUSH", 461 | "args": [ 462 | { 463 | "prim": "mutez" 464 | }, 465 | { 466 | "int": "1000000" 467 | } 468 | ] 469 | }, 470 | { 471 | "prim": "UNIT" 472 | }, 473 | { 474 | "prim": "RIGHT", 475 | "args": [ 476 | { 477 | "prim": "lambda", 478 | "args": [ 479 | { 480 | "prim": "unit" 481 | }, 482 | { 483 | "prim": "list", 484 | "args": [ 485 | { 486 | "prim": "operation" 487 | } 488 | ] 489 | } 490 | ] 491 | } 492 | ] 493 | }, 494 | { 495 | "prim": "TRANSFER_TOKENS" 496 | }, 497 | { 498 | "prim": "CONS" 499 | }, 500 | { 501 | "prim": "PAIR" 502 | } 503 | ] 504 | ] 505 | }, 506 | { 507 | "prim": "DIP", 508 | "args": [ 509 | [ 510 | { 511 | "prim": "DROP" 512 | }, 513 | { 514 | "prim": "DROP" 515 | } 516 | ] 517 | ] 518 | } 519 | ] 520 | ] 521 | } 522 | ], 523 | "storage": { 524 | "prim": "Unit" 525 | } 526 | } 527 | }`) 528 | 529 | var origination2 rpc.Origination 530 | err = json.Unmarshal(originationJSON2, &origination2) 531 | testutils.CheckErr(t, false, "", err) 532 | 533 | type want struct { 534 | err bool 535 | errContains string 536 | operation string 537 | } 538 | 539 | cases := []struct { 540 | name string 541 | input rpc.Origination 542 | want want 543 | }{ 544 | { 545 | "is successful", 546 | origination, 547 | want{ 548 | false, 549 | "", 550 | "6d0054013ef6636fe99989a26006622bf270be0b1485ed0ad4de128457c302000000000024020000001f0500046c00000004256162630501036c050202000000080317053d036d034200000002030b", 551 | }, 552 | }, 553 | { 554 | "is successful 2", 555 | origination2, 556 | want{ 557 | false, 558 | "", 559 | "6d0054013ef6636fe99989a26006622bf270be0b14859610d3de12af7c8e040000000000ef02000000ea0500036c0501036c050202000000db0321051f0200000002031703160743036e01000000244b54314d384d5374774131523553754778325636414d7667643864474146596345556d7505550764085e036c055f036d0000000325646f046c000000082564656661756c74072f020000001807430368010000000d74797065206d69736d6174636803270200000051020000000f071f00020200000002032105700003053d036d020000000f071f00020200000002032105700003071f0003020000000203200743036a0080897a034f0544075e036c055f036d034d031b0342051f02000000040320032000000002030b", 560 | }, 561 | }, 562 | } 563 | 564 | for _, tt := range cases { 565 | t.Run(tt.name, func(t *testing.T) { 566 | origination, err := forgeOrigination(tt.input) 567 | testutils.CheckErr(t, tt.want.err, tt.want.errContains, err) 568 | assert.Equal(t, tt.want.operation, hex.EncodeToString(origination)) 569 | }) 570 | } 571 | } 572 | 573 | func Test_Forge_Transaction(t *testing.T) { 574 | transactionJSON := []byte(`{ 575 | "kind": "transaction", 576 | "source": "tz1NXjqkurAmpKJEF76T58oyNsy3hWK7mk8e", 577 | "fee": "22100", 578 | "counter": "377727", 579 | "gas_limit": "218465", 580 | "storage_limit": "668", 581 | "amount": "0", 582 | "destination": "KT1SkmB19o8nfhRvG9LL7TjDfX2Bm1nCuYoY" 583 | }`) 584 | 585 | var transaction rpc.Transaction 586 | err := json.Unmarshal(transactionJSON, &transaction) 587 | testutils.CheckErr(t, false, "", err) 588 | 589 | transactionJSON2 := []byte(`{ 590 | "kind": "transaction", 591 | "source": "tz1SJJY253HoEda8PS5vvfHVtyghgK3CTS2z", 592 | "fee": "2966", 593 | "counter": "133558", 594 | "gas_limit": "26271", 595 | "storage_limit": "0", 596 | "amount": "0", 597 | "destination": "KT1XdCkJncWfGvqf1NdbK2HBRTvRcHhJtNx5", 598 | "parameters": { 599 | "entrypoint": "do", 600 | "value": [ 601 | { 602 | "prim": "RENAME" 603 | }, 604 | { 605 | "prim": "NIL", 606 | "args": [ 607 | { 608 | "prim": "operation" 609 | } 610 | ] 611 | }, 612 | { 613 | "prim": "PUSH", 614 | "args": [ 615 | { 616 | "prim": "key_hash" 617 | }, 618 | { 619 | "string": "tz2L2HuhaaSnf6ShEDdhTEAr5jGPWPNwpvcB" 620 | } 621 | ] 622 | }, 623 | { 624 | "prim": "IMPLICIT_ACCOUNT" 625 | }, 626 | { 627 | "prim": "PUSH", 628 | "args": [ 629 | { 630 | "prim": "mutez" 631 | }, 632 | { 633 | "int": "2" 634 | } 635 | ] 636 | }, 637 | { 638 | "prim": "UNIT" 639 | }, 640 | { 641 | "prim": "TRANSFER_TOKENS" 642 | }, 643 | { 644 | "prim": "CONS" 645 | }, 646 | { 647 | "prim": "DIP", 648 | "args": [ 649 | [ 650 | { 651 | "prim": "DROP" 652 | } 653 | ] 654 | ] 655 | } 656 | ] 657 | } 658 | }`) 659 | 660 | var transaction2 rpc.Transaction 661 | err = json.Unmarshal(transactionJSON2, &transaction2) 662 | testutils.CheckErr(t, false, "", err) 663 | 664 | type want struct { 665 | err bool 666 | errContains string 667 | operation string 668 | } 669 | 670 | cases := []struct { 671 | name string 672 | input rpc.Transaction 673 | want want 674 | }{ 675 | { 676 | "is successful json 1", 677 | transaction, 678 | want{ 679 | false, 680 | "", 681 | "6c001fb7d0a599ddca61b88dc203eeefbac341422cdfd4ac01ff8617e1aa0d9c050001c756189bc655cc487d57e5fefe482449dbe00c390000", 682 | }, 683 | }, 684 | { 685 | "is successful json 2", 686 | transaction2, 687 | want{ 688 | false, 689 | "", 690 | "6c00490dc9520ec45270f240a3cc4f07aec76adc358d9617b693089fcd01000001fcc0bee1480bfca3a80481904cee4099400b1c8d00ff020000004f020000004a0358053d036d0743035d0100000024747a324c324875686161536e663653684544646854454172356a475057504e7770766342031e0743036a0002034f034d031b051f02000000020320", 691 | }, 692 | }, 693 | } 694 | 695 | for _, tt := range cases { 696 | t.Run(tt.name, func(t *testing.T) { 697 | transaction, err := forgeTransaction(tt.input) 698 | testutils.CheckErr(t, tt.want.err, tt.want.errContains, err) 699 | assert.Equal(t, tt.want.operation, hex.EncodeToString(transaction)) 700 | }) 701 | } 702 | } 703 | 704 | func Test_Forge_Reveal(t *testing.T) { 705 | revealJSON := []byte(`{ 706 | "kind": "reveal", 707 | "source": "tz1f2MeahW6XMLcfHJSU5VH8USC4EuFiwdhx", 708 | "fee": "1257", 709 | "counter": "5", 710 | "gas_limit": "10000", 711 | "storage_limit": "0", 712 | "public_key": "edpkuEmaQSYKgDj5k9wfE3bTxjfjoG9k5YvRmYZsGf2bjEymZKkzNn" 713 | }`) 714 | 715 | var reveal rpc.Reveal 716 | err := json.Unmarshal(revealJSON, &reveal) 717 | testutils.CheckErr(t, false, "", err) 718 | 719 | type want struct { 720 | err bool 721 | errContains string 722 | operation string 723 | } 724 | 725 | cases := []struct { 726 | name string 727 | input rpc.Reveal 728 | want want 729 | }{ 730 | { 731 | "is successful", 732 | reveal, 733 | want{ 734 | false, 735 | "", 736 | "6b00d4a35d6c49ffbaa32b40e96c844dc485b0cdb5fae90905904e00004e7097e206a9afa864475095b58009014f9c24efd54c5d40240c1e807b4ab80c", 737 | }, 738 | }, 739 | } 740 | 741 | for _, tt := range cases { 742 | t.Run(tt.name, func(t *testing.T) { 743 | reveal, err := forgeReveal(tt.input) 744 | testutils.CheckErr(t, tt.want.err, tt.want.errContains, err) 745 | assert.Equal(t, tt.want.operation, hex.EncodeToString(reveal)) 746 | }) 747 | } 748 | } 749 | 750 | func Test_IntExpression(t *testing.T) { 751 | val, err := IntExpression(9) 752 | testutils.CheckErr(t, false, "", err) 753 | assert.Equal(t, "exprtvAzqNE9zfpBLL9nKEaY1Dd2rznyG9iTFtECJvDkuub1bj3XvW", val) 754 | 755 | val, err = IntExpression(-9) 756 | testutils.CheckErr(t, false, "", err) 757 | assert.Equal(t, "exprvH9jru3NJN4ZTNwwkCdC1PPLkWLWCoe6JxhcJ3a39mD5Bd4NH4", val) 758 | } 759 | 760 | func Test_NatExpression(t *testing.T) { 761 | val, err := NatExpression(9) 762 | testutils.CheckErr(t, false, "", err) 763 | assert.Equal(t, "exprtvAzqNE9zfpBLL9nKEaY1Dd2rznyG9iTFtECJvDkuub1bj3XvW", val) 764 | } 765 | 766 | func Test_AddressExpression(t *testing.T) { 767 | val, err := AddressExpression(`tz1S82rGFZK8cVbNDpP1Hf9VhTUa4W8oc2WV`) 768 | testutils.CheckErr(t, false, "", err) 769 | assert.Equal(t, "expruwEtkquVj9E92Wc7KTFMSnCqVGZ4KPngpspNmRTm6rX6KZbcvH", val) 770 | } 771 | 772 | func Test_StringExpression(t *testing.T) { 773 | val, err := StringExpression("Tezos Tacos Nachos") 774 | testutils.CheckErr(t, false, "", err) 775 | assert.Equal(t, "expruGmscHLuUazE7d79EepWCnDuPJreo8R87wsDGUgKAuH4E5ayEj", val) 776 | } 777 | 778 | func Test_KeyHashExpression(t *testing.T) { 779 | val, err := KeyHashExpression(`tz1eEnQhbwf6trb8Q8mPb2RaPkNk2rN7BKi8`) 780 | testutils.CheckErr(t, false, "", err) 781 | assert.Equal(t, "expruqnFVtyPKd2KcrjkiJTaqE1WU1fEf8K1ajHvzgKz5pcc5sZyjn", val) 782 | } 783 | 784 | func Test_BytesExpression(t *testing.T) { 785 | v, _ := hex.DecodeString(`0a0a0a`) 786 | val, err := BytesExpression(v) 787 | testutils.CheckErr(t, false, "", err) 788 | assert.Equal(t, "exprunb7V121UYKTTbQGj6UQrpgXcZE3F71TrNMUkw9WtARMzht9tN", val) 789 | } 790 | 791 | func Test_MichelineExpression(t *testing.T) { 792 | v := `{ "prim": "Pair", "args": [ { "int": "1" }, { "int": "12" } ] }` 793 | val, err := MichelineExpression(v) 794 | testutils.CheckErr(t, false, "", err) 795 | assert.Equal(t, "exprupozG51AtT7yZUy5sg6VbJQ4b9omAE1PKD2PXvqi2YBuZqoKG3", val) 796 | } 797 | --------------------------------------------------------------------------------