├── .gitignore ├── pkg ├── transaction │ ├── signer.go │ ├── util.go │ ├── transaction.go │ ├── errors.go │ └── ethcontroller.go ├── rpc │ ├── query_test.go │ ├── handler.go │ ├── query.go │ ├── common │ │ └── methods.go │ ├── eth │ │ └── methods.go │ ├── v1 │ │ └── methods.go │ └── methods.go ├── governance │ ├── config.go │ ├── util.go │ ├── cli.go │ ├── api.go │ ├── request.go │ ├── signing.go │ ├── signing_test.go │ ├── eip712.go │ └── types.go ├── common │ ├── store.go │ ├── presentation.go │ ├── values.go │ ├── chain-id.go │ └── numeric.go ├── keys │ ├── keystore_test.go │ ├── encoding.go │ ├── mnemonic.go │ ├── mnemonic_test.go │ ├── keys.go │ └── bls_test.go ├── mnemonic │ └── mnemonic.go ├── account │ ├── removal.go │ ├── account_test.go │ ├── creation.go │ ├── export.go │ └── import.go ├── console │ ├── hmy.go │ ├── jsre │ │ ├── deps │ │ │ └── deps.go │ │ ├── completion_test.go │ │ ├── completion.go │ │ ├── jsre_test.go │ │ ├── pretty.go │ │ └── jsre.go │ └── prompt │ │ └── prompter.go ├── validation │ ├── validation_test.go │ └── validator.go ├── sharding │ └── structure.go ├── address │ └── address.go ├── store │ └── local.go └── ledger │ ├── hw_wallet.go │ └── nano_S_driver.go ├── e2e └── keystore_test.go ├── cmd ├── subcommands │ ├── completion.go │ ├── delegation.go │ ├── failures.go │ ├── custom-flags.go │ ├── balance.go │ ├── command.go │ ├── validator.go │ ├── governance.go │ ├── utility.go │ ├── values.go │ ├── blockchain.go │ └── root.go └── main.go ├── scripts ├── stress-test └── hmy.sh ├── Makefile ├── .github └── workflows │ ├── test-build.yml │ └── hmybuild.yml └── go.mod /.gitignore: -------------------------------------------------------------------------------- 1 | hmy 2 | hmy-docs 3 | dist 4 | .idea/ 5 | *.key 6 | hmy_version 7 | -------------------------------------------------------------------------------- /pkg/transaction/signer.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | type SignerImpl int 4 | 5 | const ( 6 | Software SignerImpl = iota 7 | Ledger 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/rpc/query_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestRPCRequest(t *testing.T) { 9 | fmt.Println("hell rpc?") 10 | } 11 | -------------------------------------------------------------------------------- /e2e/keystore_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestKeyStore(t *testing.T) { 9 | fmt.Println("Hello world") 10 | //t.Errorf("Testing pipeline") 11 | } 12 | -------------------------------------------------------------------------------- /pkg/governance/config.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | const ( 4 | backendAddress = "https://hub.snapshot.org/api/" 5 | urlMessage = backendAddress + "msg" 6 | version = "0.1.4" 7 | name = "snapshot" 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/common/store.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/harmony-one/harmony/accounts/keystore" 5 | ) 6 | 7 | func KeyStoreForPath(p string) *keystore.KeyStore { 8 | return keystore.NewKeyStore(p, ScryptN, ScryptP) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/governance/util.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | func indent(v interface{}) string { 9 | b, err := json.MarshalIndent(v, "", " ") 10 | if err != nil { 11 | return fmt.Sprintf("%#v", v) 12 | } 13 | return string(b) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/keys/keystore_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestKeyStore(t *testing.T) { 9 | fmt.Println("Hello world 3") 10 | // t.Errorf("Testing pipeline") 11 | } 12 | 13 | func TestAnother(t *testing.T) { 14 | fmt.Println("Another test run") 15 | } 16 | -------------------------------------------------------------------------------- /pkg/mnemonic/mnemonic.go: -------------------------------------------------------------------------------- 1 | package mnemonic 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/tyler-smith/go-bip39" 7 | ) 8 | 9 | var ( 10 | InvalidMnemonic = errors.New("invalid mnemonic given") 11 | ) 12 | 13 | func Generate() string { 14 | entropy, _ := bip39.NewEntropy(256) 15 | mnemonic, _ := bip39.NewMnemonic(entropy) 16 | return mnemonic 17 | } 18 | -------------------------------------------------------------------------------- /pkg/transaction/util.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | func StringToByte(dataStr string) ([]byte, error) { 10 | if len(dataStr) == 0 { 11 | return []byte{}, nil 12 | } 13 | if !strings.HasPrefix(dataStr, "0x") { 14 | return nil, fmt.Errorf("invalid data literal: %q", dataStr) 15 | } 16 | return hex.DecodeString(dataStr[2:]) 17 | } 18 | -------------------------------------------------------------------------------- /pkg/rpc/handler.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | type Reply map[string]interface{} 4 | 5 | type T interface { 6 | SendRPC(string, []interface{}) (Reply, error) 7 | } 8 | 9 | type HTTPMessenger struct { 10 | node string 11 | } 12 | 13 | func (M *HTTPMessenger) SendRPC(meth string, params []interface{}) (Reply, error) { 14 | return Request(meth, M.node, params) 15 | } 16 | 17 | func NewHTTPHandler(node string) *HTTPMessenger { 18 | // TODO Sanity check the URL for HTTP 19 | return &HTTPMessenger{node} 20 | } 21 | -------------------------------------------------------------------------------- /pkg/keys/encoding.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | secp256k1 "github.com/btcsuite/btcd/btcec" 5 | "github.com/ethereum/go-ethereum/common/hexutil" 6 | ) 7 | 8 | type Dump struct { 9 | PrivateKey, PublicKeyCompressed, PublicKey string 10 | } 11 | 12 | func EncodeHex(sk *secp256k1.PrivateKey, pk *secp256k1.PublicKey) *Dump { 13 | p0 := sk.Serialize() 14 | p1 := pk.SerializeCompressed() 15 | p2 := pk.SerializeUncompressed() 16 | return &Dump{hexutil.Encode(p0), hexutil.Encode(p1), hexutil.Encode(p2)} 17 | } 18 | -------------------------------------------------------------------------------- /pkg/common/presentation.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | func JSONPrettyFormat(in string) string { 9 | var out bytes.Buffer 10 | err := json.Indent(&out, []byte(in), "", " ") 11 | if err != nil { 12 | return in 13 | } 14 | return out.String() 15 | } 16 | 17 | // returns "{}" on failure case 18 | func ToJSONUnsafe(payload interface{}, pretty bool) string { 19 | j, err := json.Marshal(payload) 20 | if err != nil { 21 | return "{}" 22 | } 23 | if pretty { 24 | return JSONPrettyFormat(string(j)) 25 | } 26 | return string(j) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/subcommands/completion.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | func init() { 10 | cmdStaking := &cobra.Command{ 11 | Use: "completion", 12 | Short: "Generates bash completion scripts", 13 | Long: `To load completion, run: 14 | 15 | . <(hmy completion) 16 | 17 | Add the line to your ~/.bashrc to enable completiony for each bash session. 18 | `, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | RootCmd.GenBashCompletion(os.Stdout) 21 | return nil 22 | }, 23 | } 24 | 25 | cmdStaking.AddCommand(stakingSubCommands()...) 26 | RootCmd.AddCommand(cmdStaking) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/governance/cli.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/harmony-one/harmony/accounts" 7 | "github.com/harmony-one/harmony/accounts/keystore" 8 | ) 9 | 10 | func DoVote(keyStore *keystore.KeyStore, account accounts.Account, vote Vote) error { 11 | typedData, err := vote.ToEIP712() 12 | if err != nil { 13 | return err 14 | } 15 | sig, err := signTypedData(keyStore, account, typedData) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | result, err := submitMessage(account.Address.String(), typedData, sig) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | fmt.Println(indent(result)) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/account/removal.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | "github.com/harmony-one/go-sdk/pkg/common" 9 | "github.com/harmony-one/go-sdk/pkg/store" 10 | "github.com/mitchellh/go-homedir" 11 | ) 12 | 13 | // RemoveAccount - removes an account from the keystore 14 | func RemoveAccount(name string) error { 15 | accountExists := store.DoesNamedAccountExist(name) 16 | 17 | if !accountExists { 18 | return fmt.Errorf("account %s doesn't exist", name) 19 | } 20 | 21 | uDir, _ := homedir.Dir() 22 | hmyCLIDir := path.Join(uDir, common.DefaultConfigDirName, common.DefaultConfigAccountAliasesDirName) 23 | accountDir := fmt.Sprintf("%s/%s", hmyCLIDir, name) 24 | os.RemoveAll(accountDir) 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/governance/api.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | func submitMessage(address string, typedData *TypedData, sign string) (map[string]interface{}, error) { 10 | data, err := typedData.String() 11 | if err != nil { 12 | return nil, errors.Wrapf(err, "could not encode EIP712 data") 13 | } 14 | 15 | type body struct { 16 | Address string `json:"address"` 17 | Sig string `json:"sig"` 18 | JsonData json.RawMessage `json:"data"` 19 | } 20 | 21 | message, err := json.Marshal(body{ 22 | Address: address, 23 | Sig: sign, 24 | JsonData: json.RawMessage(data), 25 | }) 26 | if err != nil { 27 | return nil, errors.Wrapf(err, "could not encode body") 28 | } 29 | return postAndParse(urlMessage, message) 30 | } 31 | -------------------------------------------------------------------------------- /scripts/stress-test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ieu 4 | 5 | source ../harmony/scripts/setup_bls_build_flags.sh 6 | 7 | sender='one1yc06ghr2p8xnl2380kpfayweguuhxdtupkhqzw' 8 | receiver='one1q6gkzcap0uruuu8r6sldxuu47pd4ww9w9t7tg6' 9 | shard_zero='https://api.s0.b.hmny.io/' 10 | shard_one='https://api.s0.b.hmny.io/' 11 | 12 | direct_node='http://52.27.34.100:9500' 13 | 14 | function c { 15 | printf "%s\n" "$*" | bc 16 | } 17 | 18 | # Shard 0 to 0 19 | for iter in $(seq 100); do 20 | rand=$(grep -m1 -ao '[0-9]' /dev/urandom | sed s/0/3/ | head -n1) 21 | value=$(c "${iter}/100") 22 | bump=$(c "${value}+${rand}") 23 | amount=$(printf "%.2f" ${bump}) 24 | ./hmy --node=${shard_zero} \ 25 | transfer --from ${sender} --to ${receiver} \ 26 | --from-shard 0 --to-shard 0 --amount ${amount} \ 27 | --passphrase='' & 28 | done 29 | -------------------------------------------------------------------------------- /cmd/subcommands/delegation.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/harmony-one/go-sdk/pkg/rpc" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var ( 9 | delegationSubCmds = []*cobra.Command{{ 10 | Use: "by-delegator", 11 | Short: "Get all delegations by a delegator", 12 | Args: cobra.ExactArgs(1), 13 | PreRunE: validateAddress, 14 | RunE: func(cmd *cobra.Command, args []string) error { 15 | noLatest = true 16 | return request(rpc.Method.GetDelegationsByDelegator, []interface{}{addr.address}) 17 | }, 18 | }, { 19 | Use: "by-validator", 20 | Short: "Get all delegations for a validator", 21 | Args: cobra.ExactArgs(1), 22 | PreRunE: validateAddress, 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | noLatest = true 25 | return request(rpc.Method.GetDelegationsByValidator, []interface{}{addr.address}) 26 | }, 27 | }} 28 | ) 29 | -------------------------------------------------------------------------------- /pkg/keys/mnemonic.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | 6 | secp256k1 "github.com/btcsuite/btcd/btcec" 7 | "github.com/cosmos/cosmos-sdk/crypto/keys/hd" 8 | "github.com/tyler-smith/go-bip39" 9 | ) 10 | 11 | // FromMnemonicSeedAndPassphrase mimics the Harmony JS sdk in deriving the 12 | // private, public key pair from the mnemonic, its index, and empty string password. 13 | // Note that an index k would be the k-th key generated using the same mnemonic. 14 | func FromMnemonicSeedAndPassphrase(mnemonic string, index int, coinType int) (*secp256k1.PrivateKey, *secp256k1.PublicKey) { 15 | seed := bip39.NewSeed(mnemonic, "") 16 | master, ch := hd.ComputeMastersFromSeed(seed) 17 | private, _ := hd.DerivePrivateKeyForPath( 18 | master, 19 | ch, 20 | fmt.Sprintf("44'/%d'/0'/0/%d", coinType, index), 21 | ) 22 | 23 | return secp256k1.PrivKeyFromBytes(secp256k1.S256(), private[:]) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/keys/mnemonic_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | const ( 8 | phrase = "crouch embrace tree can horn decrease until boil ice edit eagle chimney" 9 | index = 0 10 | publicKey = "0x030b64624e60c6e6758711fdf00f7e873f40a22e647a6918ba73807fab194d09ba" 11 | privateKey = "0xda4bc68857640103942ce7dd22a9fcdb96f3cfe0380254e81352a94ac8262ed2" 12 | coinType = 1023 13 | ) 14 | 15 | func TestMnemonic(t *testing.T) { 16 | private, public := FromMnemonicSeedAndPassphrase(phrase, index, coinType) 17 | sk, pkCompressed := func() (string, string) { 18 | dump := EncodeHex(private, public) 19 | return dump.PrivateKey, dump.PublicKeyCompressed 20 | }() 21 | if sk != privateKey { 22 | t.Errorf("Private key mismatch %s != %s", sk, privateKey) 23 | } 24 | if pkCompressed != publicKey { 25 | t.Errorf("Public Compressed key mismatch %s != %s", pkCompressed, publicKey) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pkg/console/hmy.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dop251/goja" 6 | "github.com/harmony-one/harmony/accounts" 7 | "github.com/harmony-one/harmony/accounts/keystore" 8 | "github.com/harmony-one/harmony/crypto/hash" 9 | "strconv" 10 | ) 11 | 12 | func signMessageWithPassword(keyStore *keystore.KeyStore, account accounts.Account, password string, data []byte) (sign []byte, err error) { 13 | signData := append([]byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(data)))) 14 | msgHash := hash.Keccak256(append(signData, data...)) 15 | 16 | sign, err = keyStore.SignHashWithPassphrase(account, password, msgHash) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | if len(sign) != 65 { 22 | return nil, fmt.Errorf("sign error") 23 | } 24 | 25 | sign[64] += 0x1b 26 | return sign, nil 27 | } 28 | 29 | func getStringFromJsObjWithDefault(o *goja.Object, key string, def string) string { 30 | get := o.Get(key) 31 | if get == nil { 32 | return def 33 | } else { 34 | return get.String() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cmd/subcommands/failures.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/harmony-one/go-sdk/pkg/rpc" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | func init() { 9 | cmdFailures := &cobra.Command{ 10 | Use: "failures", 11 | Short: "Check in-memory record of failed transactions", 12 | Long: `Check node for its in-memory record of failed transactions`, 13 | RunE: func(cmd *cobra.Command, args []string) error { 14 | cmd.Help() 15 | return nil 16 | }, 17 | } 18 | cmdFailures.AddCommand([]*cobra.Command{{ 19 | Use: "plain", 20 | Short: "plain transaction failures", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | noLatest = true 23 | return request(rpc.Method.GetCurrentTransactionErrorSink, []interface{}{}) 24 | }, 25 | }, { 26 | Use: "staking", 27 | Short: "staking transaction failures", 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | noLatest = true 30 | return request(rpc.Method.GetCurrentStakingErrorSink, []interface{}{}) 31 | }, 32 | }, 33 | }...) 34 | RootCmd.AddCommand(cmdFailures) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | 8 | cmd "github.com/harmony-one/go-sdk/cmd/subcommands" 9 | // Need this side effect 10 | _ "github.com/harmony-one/go-sdk/pkg/store" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | version string 16 | commit string 17 | builtAt string 18 | builtBy string 19 | ) 20 | 21 | func main() { 22 | // HACK Force usage of go implementation rather than the C based one. Do the right way, see the 23 | // notes one line 66,67 of https://golang.org/src/net/net.go that say can make the decision at 24 | // build time. 25 | os.Setenv("GODEBUG", "netdns=go") 26 | cmd.VersionWrapDump = version + "-" + commit 27 | cmd.RootCmd.AddCommand(&cobra.Command{ 28 | Use: "version", 29 | Short: "Show version", 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | fmt.Fprintf(os.Stderr, 32 | "Harmony (C) 2020. %v, version %v-%v (%v %v)\n", 33 | path.Base(os.Args[0]), version, commit, builtBy, builtAt) 34 | os.Exit(0) 35 | return nil 36 | }, 37 | }) 38 | cmd.Execute() 39 | } 40 | -------------------------------------------------------------------------------- /pkg/console/jsre/deps/deps.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | // Package deps contains the console JavaScript dependencies Go embedded. 18 | package deps 19 | 20 | //go:generate go-bindata -pkg deps -o bindata.go bignumber.js web3.js 21 | //go:generate gofmt -w -s bindata.go 22 | -------------------------------------------------------------------------------- /pkg/account/account_test.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/store" 7 | ) 8 | 9 | func TestAccountGetsRemoved(t *testing.T) { 10 | tests := []struct { 11 | Name string 12 | Expected bool 13 | }{ 14 | {"foobar", false}, 15 | } 16 | 17 | for _, test := range tests { 18 | acc := Creation{ 19 | Name: test.Name, 20 | Passphrase: "", 21 | Mnemonic: "", 22 | HdAccountNumber: nil, 23 | HdIndexNumber: nil, 24 | } 25 | 26 | err := CreateNewLocalAccount(&acc) 27 | 28 | if err != nil { 29 | t.Errorf(`RemoveAccount("%s") failed to create keystore account %v`, test.Name, err) 30 | } 31 | 32 | exists := store.DoesNamedAccountExist(test.Name) 33 | 34 | if !exists { 35 | t.Errorf(`RemoveAccount("%s") account should exist - but it can't be found`, test.Name) 36 | } 37 | 38 | RemoveAccount(test.Name) 39 | exists = store.DoesNamedAccountExist(test.Name) 40 | 41 | if exists != test.Expected { 42 | t.Errorf(`RemoveAccount("%s") returned %v, expected %v`, test.Name, exists, test.Expected) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cmd/subcommands/custom-flags.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/harmony-one/go-sdk/pkg/address" 5 | "github.com/harmony-one/go-sdk/pkg/common" 6 | "github.com/harmony-one/go-sdk/pkg/validation" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type oneAddress struct { 11 | address string 12 | } 13 | 14 | func (oneAddress oneAddress) String() string { 15 | return oneAddress.address 16 | } 17 | 18 | func (oneAddress *oneAddress) Set(s string) error { 19 | err := validation.ValidateAddress(s) 20 | if err != nil { 21 | return err 22 | } 23 | _, err = address.Bech32ToAddress(s) 24 | if err != nil { 25 | return errors.Wrap(err, "not a valid one address") 26 | } 27 | oneAddress.address = s 28 | return nil 29 | } 30 | 31 | func (oneAddress oneAddress) Type() string { 32 | return "one-address" 33 | } 34 | 35 | type chainIDWrapper struct { 36 | chainID *common.ChainID 37 | } 38 | 39 | func (chainIDWrapper chainIDWrapper) String() string { 40 | return chainIDWrapper.chainID.Name 41 | } 42 | 43 | func (chainIDWrapper *chainIDWrapper) Set(s string) error { 44 | chain, err := common.StringToChainID(s) 45 | chainIDWrapper.chainID = chain 46 | if err != nil { 47 | return err 48 | } 49 | return nil 50 | } 51 | 52 | func (chainIDWrapper chainIDWrapper) Type() string { 53 | return "chain-id" 54 | } 55 | -------------------------------------------------------------------------------- /pkg/common/values.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/harmony-one/harmony/accounts/keystore" 8 | ) 9 | 10 | const ( 11 | DefaultConfigDirName = ".hmy_cli" 12 | DefaultConfigAccountAliasesDirName = "account-keys" 13 | DefaultCommandAliasesDirName = "command" 14 | DefaultPassphrase = "" 15 | JSONRPCVersion = "2.0" 16 | Secp256k1PrivateKeyBytesLength = 32 17 | ) 18 | 19 | var ( 20 | ScryptN = keystore.StandardScryptN 21 | ScryptP = keystore.StandardScryptP 22 | DebugRPC = false 23 | DebugTransaction = false 24 | ErrNotAbsPath = errors.New("keypath is not absolute path") 25 | ErrBadKeyLength = errors.New("Invalid private key (wrong length)") 26 | ErrFoundNoKey = errors.New("found no bls key file") 27 | ErrFoundNoPass = errors.New("found no passphrase file") 28 | ) 29 | 30 | func init() { 31 | if _, enabled := os.LookupEnv("HMY_RPC_DEBUG"); enabled != false { 32 | DebugRPC = true 33 | } 34 | if _, enabled := os.LookupEnv("HMY_TX_DEBUG"); enabled != false { 35 | DebugTransaction = true 36 | } 37 | if _, enabled := os.LookupEnv("HMY_ALL_DEBUG"); enabled != false { 38 | EnableAllVerbose() 39 | } 40 | } 41 | 42 | // EnableAllVerbose sets debug vars to true 43 | func EnableAllVerbose() { 44 | DebugRPC = true 45 | DebugTransaction = true 46 | } 47 | -------------------------------------------------------------------------------- /pkg/account/creation.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/keys" 7 | "github.com/harmony-one/go-sdk/pkg/mnemonic" 8 | "github.com/harmony-one/go-sdk/pkg/store" 9 | ) 10 | 11 | var ( 12 | AccountByNameExists = errors.New("name chosen for account already exists") 13 | ) 14 | 15 | type Creation struct { 16 | Name string 17 | Passphrase string 18 | Mnemonic string 19 | HdAccountNumber *uint32 20 | HdIndexNumber *uint32 21 | CoinType *uint32 22 | } 23 | 24 | func New() string { 25 | return "New Account" 26 | } 27 | 28 | func IsValidPassphrase(pass string) bool { 29 | return true 30 | } 31 | 32 | // CreateNewLocalAccount assumes all the inputs are valid, legitmate 33 | func CreateNewLocalAccount(candidate *Creation) error { 34 | ks := store.FromAccountName(candidate.Name) 35 | if candidate.Mnemonic == "" { 36 | candidate.Mnemonic = mnemonic.Generate() 37 | } 38 | 39 | index := uint32(0) 40 | if candidate.HdIndexNumber != nil { 41 | index = *candidate.HdIndexNumber 42 | } 43 | 44 | coinType := uint32(1023) 45 | if candidate.CoinType != nil { 46 | coinType = *candidate.CoinType 47 | } 48 | 49 | private, _ := keys.FromMnemonicSeedAndPassphrase(candidate.Mnemonic, int(index), int(coinType)) 50 | _, err := ks.ImportECDSA(private.ToECDSA(), candidate.Passphrase) 51 | if err != nil { 52 | return err 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /pkg/validation/validation_test.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/sharding" 7 | ) 8 | 9 | func TestIsValidAddress(t *testing.T) { 10 | tests := []struct { 11 | str string 12 | exp bool 13 | }{ 14 | {"one1ay37rp2pc3kjarg7a322vu3sa8j9puahg679z3", true}, 15 | {"0x7c41E0668B551f4f902cFaec05B5Bdca68b124CE", true}, 16 | {"onefoofoo", false}, 17 | {"0xbarbar", false}, 18 | {"dsasdadsasaadsas", false}, 19 | {"32312123213213212321", false}, 20 | } 21 | 22 | for _, test := range tests { 23 | err := ValidateAddress(test.str) 24 | valid := false 25 | 26 | if err == nil { 27 | valid = true 28 | } 29 | 30 | if valid != test.exp { 31 | t.Errorf(`ValidateAddress("%s") returned %v, expected %v`, test.str, valid, test.exp) 32 | } 33 | } 34 | } 35 | 36 | func TestIsValidShard(t *testing.T) { 37 | if err := ValidateNodeConnection("http://localhost:9500"); err != nil { 38 | t.Skip() 39 | } 40 | s, _ := sharding.Structure("http://localhost:9500") 41 | 42 | tests := []struct { 43 | shardID uint32 44 | exp bool 45 | }{ 46 | {0, true}, 47 | {1, true}, 48 | {98, false}, 49 | {99, false}, 50 | } 51 | 52 | for _, test := range tests { 53 | valid := ValidShardID(test.shardID, uint32(len(s))) 54 | 55 | if valid != test.exp { 56 | t.Errorf("ValidShardID(%d) returned %v, expected %v", test.shardID, valid, test.exp) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /pkg/governance/request.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/pkg/errors" 11 | "github.com/valyala/fastjson" 12 | ) 13 | 14 | func postAndParse(url string, postData []byte) (map[string]interface{}, error) { 15 | resp, err := http.Post(string(url), "application/json", bytes.NewReader(postData)) 16 | if err != nil { 17 | return nil, errors.Wrapf(err, "could not send post request") 18 | } 19 | defer resp.Body.Close() 20 | return parseAndUnmarshal(resp) 21 | } 22 | 23 | func parseAndUnmarshal(resp *http.Response) (map[string]interface{}, error) { 24 | bodyData, err := ioutil.ReadAll(resp.Body) 25 | if err != nil { 26 | return nil, err 27 | } 28 | // the 500 is used for errors like `invalid signature`, `not in voting window`, etc. 29 | if resp.StatusCode != 200 && resp.StatusCode != 500 { 30 | return nil, errors.Errorf("unexpected response code %s\nbody: %s", resp.Status, bodyData) 31 | } 32 | 33 | if fastjson.GetString(bodyData, "error") != "" { 34 | return nil, fmt.Errorf("error: %s, %s", fastjson.GetString(bodyData, "error"), fastjson.GetString(bodyData, "error_description")) 35 | } 36 | 37 | var result map[string]interface{} 38 | if err := json.Unmarshal(bodyData, &result); err != nil { 39 | return nil, errors.Wrapf(err, "could not decode result from %s", string(bodyData)) 40 | } else { 41 | return result, nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/validation/validator.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "regexp" 7 | "time" 8 | ) 9 | 10 | var ( 11 | addressValidationRegexp = regexp.MustCompile(`^(one[a-zA-Z0-9]{39})|(0x[a-fA-F0-9]{40})`) 12 | ) 13 | 14 | // ValidateAddress validates that an address is a valid bech32 address (one...) or a valid base16 address (0x...) 15 | func ValidateAddress(address string) error { 16 | matches := addressValidationRegexp.FindAllStringSubmatch(address, -1) 17 | if len(matches) == 0 { 18 | return fmt.Errorf("address supplied (%s) is not valid", address) 19 | } 20 | 21 | return nil 22 | } 23 | 24 | // ValidShardIDs validates senderShard and receiverShard against the shardCount 25 | func ValidShardIDs(senderShard uint32, receiverShard uint32, shardCount uint32) error { 26 | if !ValidShardID(senderShard, shardCount) { 27 | return fmt.Errorf(`invalid argument "%d" for "--from-shard"`, senderShard) 28 | } 29 | 30 | if !ValidShardID(receiverShard, shardCount) { 31 | return fmt.Errorf(`invalid argument "%d" for "--to-shard"`, receiverShard) 32 | } 33 | 34 | return nil 35 | } 36 | 37 | // ValidShardID validates that a shardID is within the bounds of the shardCount 38 | func ValidShardID(shardID uint32, shardCount uint32) bool { 39 | if shardID > (shardCount - 1) { 40 | return false 41 | } 42 | 43 | return true 44 | } 45 | 46 | // ValidateNodeConnection validates that the node can be connected to 47 | func ValidateNodeConnection(node string) error { 48 | timeout := time.Duration(1 * time.Second) 49 | _, err := net.DialTimeout("tcp", node, timeout) 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /scripts/hmy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | BUCKET='pub.harmony.one' 4 | OS="$(uname -s)" 5 | 6 | usage () { 7 | cat << EOT 8 | Usage: $0 [option] command 9 | 10 | Options: 11 | -d download all the binaries 12 | -h print this help 13 | Note: Arguments must be passed at the end for ./hmy to work correctly. 14 | For instance: ./hmy.sh balances --node=https://api.s0.p.hmny.io/ 15 | 16 | EOT 17 | } 18 | 19 | set_download () { 20 | local rel='mainnet' 21 | case "$OS" in 22 | Darwin) 23 | FOLDER=release/darwin-x86_64/${rel} 24 | URL=http://${BUCKET}.s3.amazonaws.com/${FOLDER} 25 | BIN=( hmy libbls384_256.dylib libcrypto.1.0.0.dylib libgmp.10.dylib libgmpxx.4.dylib libmcl.dylib ) 26 | NAMES=("${BIN[@]}") 27 | ;; 28 | Linux) 29 | URL=https://harmony.one 30 | BIN=( hmycli ) 31 | NAMES=( hmy ) 32 | ;; 33 | *) 34 | echo "${OS} not supported." 35 | exit 2 36 | ;; 37 | esac 38 | } 39 | 40 | do_download () { 41 | # download all the binaries 42 | for i in "${!BIN[@]}"; do 43 | rm -f ${NAMES[i]} 44 | curl -L ${URL}/${BIN[i]} -o ${NAMES[i]} 45 | done 46 | chmod +x hmy 47 | } 48 | 49 | while getopts "dh" opt; do 50 | case ${opt} in 51 | d) 52 | set_download 53 | do_download 54 | exit 0 55 | ;; 56 | h|*) 57 | usage 58 | exit 1 59 | ;; 60 | esac 61 | done 62 | 63 | shift $((OPTIND-1)) 64 | 65 | if [ "$OS" = "Linux" ]; then 66 | ./hmy "$@" 67 | else 68 | DYLD_FALLBACK_LIBRARY_PATH="$(pwd)" ./hmy "$@" 69 | fi 70 | -------------------------------------------------------------------------------- /pkg/keys/keys.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "strings" 8 | 9 | ethCommon "github.com/ethereum/go-ethereum/common" 10 | "github.com/harmony-one/go-sdk/pkg/address" 11 | "github.com/harmony-one/harmony/accounts/keystore" 12 | 13 | // "github.com/ethereum/go-ethereum/crypto" 14 | 15 | homedir "github.com/mitchellh/go-homedir" 16 | ) 17 | 18 | func checkAndMakeKeyDirIfNeeded() string { 19 | userDir, _ := homedir.Dir() 20 | hmyCLIDir := path.Join(userDir, ".hmy_cli", "keystore") 21 | if _, err := os.Stat(hmyCLIDir); os.IsNotExist(err) { 22 | // Double check with Leo what is right file persmission 23 | os.Mkdir(hmyCLIDir, 0700) 24 | } 25 | 26 | return hmyCLIDir 27 | } 28 | 29 | func ListKeys(keystoreDir string) { 30 | hmyCLIDir := checkAndMakeKeyDirIfNeeded() 31 | scryptN := keystore.StandardScryptN 32 | scryptP := keystore.StandardScryptP 33 | ks := keystore.NewKeyStore(hmyCLIDir, scryptN, scryptP) 34 | // keystore.KeyStore 35 | allAccounts := ks.Accounts() 36 | fmt.Printf("Harmony Address:%s File URL:\n", strings.Repeat(" ", ethCommon.AddressLength*2)) 37 | for _, account := range allAccounts { 38 | fmt.Printf("%s\t\t %s\n", address.ToBech32(account.Address), account.URL) 39 | } 40 | } 41 | 42 | func AddNewKey(password string) { 43 | hmyCLIDir := checkAndMakeKeyDirIfNeeded() 44 | scryptN := keystore.StandardScryptN 45 | scryptP := keystore.StandardScryptP 46 | ks := keystore.NewKeyStore(hmyCLIDir, scryptN, scryptP) 47 | account, err := ks.NewAccount(password) 48 | if err != nil { 49 | fmt.Printf("new account error: %v\n", err) 50 | } 51 | fmt.Printf("account: %s\n", address.ToBech32(account.Address)) 52 | fmt.Printf("URL: %s\n", account.URL) 53 | } 54 | -------------------------------------------------------------------------------- /pkg/account/export.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "path/filepath" 7 | 8 | "github.com/harmony-one/go-sdk/pkg/store" 9 | "github.com/harmony-one/harmony/accounts" 10 | ) 11 | 12 | var ( 13 | ErrAddressNotFound = errors.New("account was not found in keystore") 14 | ) 15 | 16 | func ExportPrivateKey(address, passphrase string) error { 17 | ks := store.FromAddress(address) 18 | if ks == nil { 19 | return ErrAddressNotFound 20 | } 21 | account := ks.Accounts()[0] 22 | _, key, err := ks.GetDecryptedKey(accounts.Account{Address: account.Address}, passphrase) 23 | if err != nil { 24 | return err 25 | } 26 | fmt.Printf("%064x\n", key.PrivateKey.D) 27 | return nil 28 | } 29 | 30 | func VerifyPassphrase(address, passphrase string) (bool, error) { 31 | ks := store.FromAddress(address) 32 | if ks == nil { 33 | return false, ErrAddressNotFound 34 | } 35 | account := ks.Accounts()[0] 36 | _, _, err := ks.GetDecryptedKey(accounts.Account{Address: account.Address}, passphrase) 37 | if err != nil { 38 | return false, err 39 | } 40 | return true, nil 41 | } 42 | 43 | func ExportKeystore(address, path, passphrase string) (string, error) { 44 | ks := store.FromAddress(address) 45 | if ks == nil { 46 | return "", ErrAddressNotFound 47 | } 48 | account := ks.Accounts()[0] 49 | dirPath, err := filepath.Abs(path) 50 | if err != nil { 51 | return "", err 52 | } 53 | outFile := filepath.Join(dirPath, fmt.Sprintf("%s.key", address)) 54 | keyFile, err := ks.Export(accounts.Account{Address: account.Address}, passphrase, passphrase) 55 | if err != nil { 56 | return "", err 57 | } 58 | e := writeToFile(outFile, string(keyFile)) 59 | if e != nil { 60 | return "", e 61 | } 62 | return outFile, nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/common/chain-id.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/big" 7 | "strconv" 8 | ) 9 | 10 | // ChainID is a wrapper around the human name for a chain and the actual Big.Int used 11 | type ChainID struct { 12 | Name string `json:"-"` 13 | Value *big.Int `json:"chain-as-number"` 14 | } 15 | 16 | type chainIDList struct { 17 | MainNet ChainID `json:"mainnet"` 18 | TestNet ChainID `json:"testnet"` 19 | PangaeaNet ChainID `json:"pangaea"` 20 | PartnerNet ChainID `json:"partner"` 21 | StressNet ChainID `json:"stress"` 22 | } 23 | 24 | // Chain is an enumeration of the known Chain-IDs 25 | var Chain = chainIDList{ 26 | MainNet: ChainID{"mainnet", big.NewInt(1)}, 27 | TestNet: ChainID{"testnet", big.NewInt(2)}, 28 | PangaeaNet: ChainID{"pangaea", big.NewInt(3)}, 29 | PartnerNet: ChainID{"partner", big.NewInt(4)}, 30 | StressNet: ChainID{"stress", big.NewInt(5)}, 31 | } 32 | 33 | func (c chainIDList) String() string { 34 | s, _ := json.MarshalIndent(c, "", " ") 35 | return string(s) 36 | } 37 | 38 | // StringToChainID returns the ChainID wrapper for the given human name of a chain-id 39 | func StringToChainID(name string) (*ChainID, error) { 40 | switch name { 41 | case "mainnet": 42 | return &Chain.MainNet, nil 43 | case "testnet": 44 | return &Chain.TestNet, nil 45 | case "pangaea": 46 | return &Chain.PangaeaNet, nil 47 | case "devnet": 48 | return &Chain.PartnerNet, nil 49 | case "partner": 50 | return &Chain.PartnerNet, nil 51 | case "stressnet": 52 | return &Chain.StressNet, nil 53 | case "dryrun": 54 | return &Chain.MainNet, nil 55 | default: 56 | if chainID, err := strconv.Atoi(name); err == nil && chainID >= 0 { 57 | return &ChainID{Name: fmt.Sprintf("%d", chainID), Value: big.NewInt(int64(chainID))}, nil 58 | } 59 | return nil, fmt.Errorf("unknown chain-id: %s", name) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/common/numeric.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | "regexp" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/harmony-one/harmony/numeric" 12 | ) 13 | 14 | var ( 15 | pattern, _ = regexp.Compile("[0-9]+\\.{0,1}[0-9]*[eE][-+]{0,1}[0-9]+") 16 | ) 17 | 18 | func Pow(base numeric.Dec, exp int) numeric.Dec { 19 | if exp < 0 { 20 | return Pow(numeric.NewDec(1).Quo(base), -exp) 21 | } 22 | result := numeric.NewDec(1) 23 | for { 24 | if exp%2 == 1 { 25 | result = result.Mul(base) 26 | } 27 | exp = exp >> 1 28 | if exp == 0 { 29 | break 30 | } 31 | base = base.Mul(base) 32 | } 33 | return result 34 | } 35 | 36 | func NewDecFromString(i string) (numeric.Dec, error) { 37 | if strings.HasPrefix(i, "-") { 38 | return numeric.ZeroDec(), errors.New(fmt.Sprintf("can not be negative: %s", i)) 39 | } 40 | if pattern.FindString(i) != "" { 41 | if tokens := strings.Split(i, "e"); len(tokens) > 1 { 42 | a, _ := numeric.NewDecFromStr(tokens[0]) 43 | b, _ := strconv.Atoi(tokens[1]) 44 | return a.Mul(Pow(numeric.NewDec(10), b)), nil 45 | } 46 | tokens := strings.Split(i, "E") 47 | a, _ := numeric.NewDecFromStr(tokens[0]) 48 | b, _ := strconv.Atoi(tokens[1]) 49 | return a.Mul(Pow(numeric.NewDec(10), b)), nil 50 | } else { 51 | if strings.HasPrefix(i, ".") { 52 | i = "0" + i 53 | } 54 | return numeric.NewDecFromStr(i) 55 | } 56 | } 57 | 58 | // Assumes Hex string input 59 | // Split into 2 64 bit integers to guarentee 128 bit precision 60 | func NewDecFromHex(str string) numeric.Dec { 61 | str = strings.TrimPrefix(str, "0x") 62 | half := len(str) / 2 63 | right := str[half:] 64 | r, _ := big.NewInt(0).SetString(right, 16) 65 | if half == 0 { 66 | return numeric.NewDecFromBigInt(r) 67 | } 68 | left := str[:half] 69 | l, _ := big.NewInt(0).SetString(left, 16) 70 | return numeric.NewDecFromBigInt(l).Mul( 71 | Pow(numeric.NewDec(16), len(right)), 72 | ).Add(numeric.NewDecFromBigInt(r)) 73 | } 74 | -------------------------------------------------------------------------------- /cmd/subcommands/balance.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net" 7 | "strings" 8 | 9 | "github.com/harmony-one/go-sdk/pkg/common" 10 | "github.com/harmony-one/go-sdk/pkg/rpc" 11 | "github.com/harmony-one/go-sdk/pkg/sharding" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | func init() { 16 | cmdQuery := &cobra.Command{ 17 | Use: "balances", 18 | Short: "Check account balance on all shards", 19 | Long: "Query for the latest account balance given a Harmony Address", 20 | Args: cobra.ExactArgs(1), 21 | PreRunE: validateAddress, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | if checkNodeInput(node) { 24 | balanceRPCReply, err := rpc.Request(rpc.Method.GetBalance, node, []interface{}{addr.address, "latest"}) 25 | if err != nil { 26 | return err 27 | } 28 | nodeRPCReply, err := rpc.Request(rpc.Method.GetShardID, node, []interface{}{}) 29 | if err != nil { 30 | return err 31 | } 32 | balance, _ := balanceRPCReply["result"].(string) 33 | bln := common.NewDecFromHex(balance) 34 | bln = bln.Quo(oneAsDec) 35 | var out bytes.Buffer 36 | out.WriteString("[") 37 | out.WriteString(fmt.Sprintf(`{"shard":%d, "amount":%s}`, 38 | uint32(nodeRPCReply["result"].(float64)), 39 | bln.String(), 40 | )) 41 | out.WriteString("]") 42 | fmt.Println(common.JSONPrettyFormat(out.String())) 43 | return nil 44 | } 45 | r, err := sharding.CheckAllShards(node, addr.String(), noPrettyOutput) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Println(r) 50 | return nil 51 | }, 52 | } 53 | 54 | RootCmd.AddCommand(cmdQuery) 55 | } 56 | 57 | // Check if input for --node is an IP address 58 | func checkNodeInput(node string) bool { 59 | removePrefix := strings.TrimPrefix(node, "http://") 60 | removePrefix = strings.TrimPrefix(removePrefix, "https://") 61 | possibleIP := strings.Split(removePrefix, ":")[0] 62 | return net.ParseIP(string(possibleIP)) != nil 63 | } 64 | -------------------------------------------------------------------------------- /pkg/sharding/structure.go: -------------------------------------------------------------------------------- 1 | package sharding 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/harmony-one/go-sdk/pkg/common" 9 | "github.com/harmony-one/go-sdk/pkg/rpc" 10 | "github.com/harmony-one/harmony/common/denominations" 11 | "github.com/harmony-one/harmony/numeric" 12 | ) 13 | 14 | var ( 15 | nanoAsDec = numeric.NewDec(denominations.Nano) 16 | oneAsDec = numeric.NewDec(denominations.One) 17 | ) 18 | 19 | // RPCRoutes reflects the RPC endpoints of the target network across shards 20 | type RPCRoutes struct { 21 | HTTP string `json:"http"` 22 | ShardID int `json:"shardID"` 23 | WS string `json:"ws"` 24 | } 25 | 26 | // Structure produces a slice of RPCRoutes for the network across shards 27 | func Structure(node string) ([]RPCRoutes, error) { 28 | type r struct { 29 | Result []RPCRoutes `json:"result"` 30 | } 31 | p, e := rpc.RawRequest(rpc.Method.GetShardingStructure, node, []interface{}{}) 32 | if e != nil { 33 | return nil, e 34 | } 35 | result := r{} 36 | if err := json.Unmarshal(p, &result); err != nil { 37 | return nil, err 38 | } 39 | return result.Result, nil 40 | } 41 | 42 | func CheckAllShards(node, oneAddr string, noPretty bool) (string, error) { 43 | var out bytes.Buffer 44 | out.WriteString("[") 45 | params := []interface{}{oneAddr, "latest"} 46 | s, err := Structure(node) 47 | if err != nil { 48 | return "", err 49 | } 50 | for i, shard := range s { 51 | balanceRPCReply, err := rpc.Request(rpc.Method.GetBalance, shard.HTTP, params) 52 | if err != nil { 53 | if common.DebugRPC { 54 | fmt.Printf("NOTE: Route %s failed.", shard.HTTP) 55 | } 56 | continue 57 | } 58 | if i != 0 { 59 | out.WriteString(",") 60 | } 61 | balance, _ := balanceRPCReply["result"].(string) 62 | bln := common.NewDecFromHex(balance) 63 | bln = bln.Quo(oneAsDec) 64 | out.WriteString(fmt.Sprintf(`{"shard":%d, "amount":%s}`, 65 | shard.ShardID, 66 | bln.String(), 67 | )) 68 | } 69 | out.WriteString("]") 70 | if noPretty { 71 | return out.String(), nil 72 | } 73 | return common.JSONPrettyFormat(out.String()), nil 74 | } 75 | -------------------------------------------------------------------------------- /pkg/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/address" 7 | "github.com/harmony-one/go-sdk/pkg/rpc" 8 | "github.com/harmony-one/harmony/core/types" 9 | "github.com/harmony-one/harmony/numeric" 10 | ) 11 | 12 | // NewTransaction - create a new Transaction based on supplied params 13 | func NewTransaction( 14 | nonce, gasLimit uint64, 15 | to *address.T, 16 | shardID, toShardID uint32, 17 | amount, gasPrice numeric.Dec, 18 | data []byte) *types.Transaction { 19 | return types.NewCrossShardTransaction(nonce, to, shardID, toShardID, amount.TruncateInt(), gasLimit, gasPrice.TruncateInt(), data[:]) 20 | } 21 | 22 | // NewEthTransaction - create a new Transaction based on supplied params 23 | func NewEthTransaction( 24 | nonce, gasLimit uint64, 25 | to address.T, 26 | amount, gasPrice numeric.Dec, 27 | data []byte) *types.EthTransaction { 28 | return types.NewEthTransaction(nonce, to, amount.TruncateInt(), gasLimit, gasPrice.TruncateInt(), data[:]) 29 | } 30 | 31 | // GetNextNonce returns the nonce on-chain (finalized transactions) 32 | func GetNextNonce(addr string, messenger rpc.T) uint64 { 33 | transactionCountRPCReply, err := 34 | messenger.SendRPC(rpc.Method.GetTransactionCount, []interface{}{address.Parse(addr), "latest"}) 35 | 36 | if err != nil { 37 | return 0 38 | } 39 | 40 | transactionCount, _ := transactionCountRPCReply["result"].(string) 41 | n, _ := big.NewInt(0).SetString(transactionCount[2:], 16) 42 | return n.Uint64() 43 | } 44 | 45 | // GetNextPendingNonce returns the nonce from the tx-pool (un-finalized transactions) 46 | func GetNextPendingNonce(addr string, messenger rpc.T) uint64 { 47 | transactionCountRPCReply, err := 48 | messenger.SendRPC(rpc.Method.GetTransactionCount, []interface{}{address.Parse(addr), "pending"}) 49 | 50 | if err != nil { 51 | return 0 52 | } 53 | 54 | transactionCount, _ := transactionCountRPCReply["result"].(string) 55 | n, _ := big.NewInt(0).SetString(transactionCount[2:], 16) 56 | return n.Uint64() 57 | } 58 | 59 | // IsValid - whether or not a tx is valid 60 | func IsValid(tx *types.Transaction) bool { 61 | return true 62 | } 63 | -------------------------------------------------------------------------------- /pkg/governance/signing.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ethereum/go-ethereum/common/hexutil" 7 | "github.com/harmony-one/harmony/accounts" 8 | "github.com/harmony-one/harmony/accounts/keystore" 9 | "github.com/harmony-one/harmony/crypto/hash" 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func encodeForSigning(typedData *TypedData) ([]byte, error) { 14 | domainSeparator, err := typedData.HashStruct("EIP712Domain", typedData.Domain.Map()) 15 | if err != nil { 16 | return nil, errors.Wrapf( 17 | err, 18 | "cannot hash the domain structure", 19 | ) 20 | } 21 | 22 | typedDataHash, err := typedData.HashStruct(typedData.PrimaryType, typedData.Message) 23 | if err != nil { 24 | return nil, errors.Wrapf( 25 | err, 26 | "cannot hash the structure", 27 | ) 28 | } 29 | 30 | rawData := []byte(fmt.Sprintf("\x19\x01%s%s", string(domainSeparator), string(typedDataHash))) 31 | return rawData, nil 32 | } 33 | 34 | // signTypedData encodes and signs EIP-712 data 35 | // it is copied over here from Geth to use our own keystore implementation 36 | func signTypedData(keyStore *keystore.KeyStore, account accounts.Account, typedData *TypedData) (string, error) { 37 | rawData, err := encodeForSigning(typedData) 38 | if err != nil { 39 | return "", errors.Wrapf( 40 | err, 41 | "cannot encode for signing", 42 | ) 43 | } 44 | 45 | msgHash := hash.Keccak256Hash(rawData) 46 | sign, err := keyStore.SignHash(account, msgHash.Bytes()) 47 | if err != nil { 48 | return "", err 49 | } 50 | if len(sign) != 65 { 51 | return "", fmt.Errorf("sign error") 52 | } 53 | sign[64] += 0x1b 54 | return hexutil.Encode(sign), nil 55 | } 56 | 57 | // func signMessage(keyStore *keystore.KeyStore, account accounts.Account, data []byte) (string, error) { 58 | // fullMessage := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(data), data) 59 | // msgHash := hash.Keccak256Hash([]byte(fullMessage)) 60 | // sign, err := keyStore.SignHash(account, msgHash.Bytes()) 61 | // if err != nil { 62 | // return "", err 63 | // } 64 | // if len(sign) != 65 { 65 | // return "", fmt.Errorf("sign error") 66 | // } 67 | // sign[64] += 0x1b 68 | // return hexutil.Encode(sign), nil 69 | // } 70 | -------------------------------------------------------------------------------- /pkg/transaction/errors.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/harmony-one/go-sdk/pkg/rpc" 9 | ) 10 | 11 | var ( 12 | errorSinkRPCs = []string{ 13 | rpc.Method.GetCurrentTransactionErrorSink, 14 | rpc.Method.GetCurrentStakingErrorSink, 15 | } 16 | ) 17 | 18 | // Error is the struct for all errors in the error sinks. 19 | // Note that StakingDirective is non-nil for staking txn errors. 20 | type Error struct { 21 | TxHashID *string `json:"tx-hash-id"` 22 | StakingDirective *string `json:"directive-kind"` // optional error field for staking 23 | ErrMessage *string `json:"error-message"` 24 | TimestampOfRejection int64 `json:"time-at-rejection"` 25 | } 26 | 27 | // Error an error with the ErrMessage and TimestampOfRejection as the message. 28 | func (e *Error) Error() error { 29 | if e.StakingDirective != nil { 30 | return fmt.Errorf("[%s] %s -- %s", 31 | *e.StakingDirective, *e.ErrMessage, time.Unix(e.TimestampOfRejection, 0).String(), 32 | ) 33 | } else { 34 | return fmt.Errorf("[Plain transaction] %s -- %s", 35 | *e.ErrMessage, time.Unix(e.TimestampOfRejection, 0).String(), 36 | ) 37 | } 38 | } 39 | 40 | // Errors ... 41 | type Errors []*Error 42 | 43 | func getTxErrorBySink(txHash, errorSinkRPC string, messenger rpc.T) (Errors, error) { 44 | var txErrors Errors 45 | response, err := messenger.SendRPC(errorSinkRPC, []interface{}{}) 46 | if err != nil { 47 | return nil, err 48 | } 49 | var allErrors []Error 50 | asJSON, _ := json.Marshal(response["result"]) 51 | _ = json.Unmarshal(asJSON, &allErrors) 52 | for i := range allErrors { 53 | txError := allErrors[i] 54 | if *txError.TxHashID == txHash { 55 | txErrors = append(txErrors, &txError) 56 | } 57 | } 58 | return txErrors, nil 59 | } 60 | 61 | // GetError returns all errors for a given (staking or plain) transaction hash. 62 | func GetError(txHash string, messenger rpc.T) (Errors, error) { 63 | for _, errorSinkRpc := range errorSinkRPCs { 64 | txErrors, err := getTxErrorBySink(txHash, errorSinkRpc, messenger) 65 | if err != nil { 66 | return Errors{}, err 67 | } 68 | if txErrors != nil { 69 | return txErrors, nil 70 | } 71 | } 72 | return Errors{}, nil 73 | } 74 | -------------------------------------------------------------------------------- /cmd/subcommands/command.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | ethereum_rpc "github.com/ethereum/go-ethereum/rpc" 5 | "github.com/harmony-one/go-sdk/pkg/common" 6 | "github.com/harmony-one/go-sdk/pkg/console" 7 | "github.com/harmony-one/go-sdk/pkg/rpc" 8 | "github.com/mitchellh/go-homedir" 9 | "github.com/spf13/cobra" 10 | "log" 11 | "os" 12 | "path" 13 | ) 14 | 15 | func init() { 16 | net := "mainnet" 17 | 18 | cmdCommand := &cobra.Command{ 19 | Use: "command", 20 | Short: "Start an interactive JavaScript environment (connect to node)", 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | return openConsole(net) 23 | }, 24 | } 25 | 26 | cmdCommand.Flags().StringVar(&net, "net", "mainnet", "used net(default: mainnet, eg: mainnet, testnet ...)") 27 | 28 | RootCmd.AddCommand(cmdCommand) 29 | } 30 | 31 | func checkAndMakeDirIfNeeded() string { 32 | userDir, _ := homedir.Dir() 33 | hmyCLIDir := path.Join(userDir, common.DefaultConfigDirName, common.DefaultCommandAliasesDirName) 34 | if _, err := os.Stat(hmyCLIDir); os.IsNotExist(err) { 35 | // Double check with Leo what is right file persmission 36 | os.Mkdir(hmyCLIDir, 0700) 37 | } 38 | 39 | return hmyCLIDir 40 | } 41 | 42 | // remoteConsole will connect to a remote node instance, attaching a JavaScript 43 | // console to it. 44 | func openConsole(net string) error { 45 | client, err := ethereum_rpc.Dial(node) 46 | if err != nil { 47 | log.Fatalf("Unable to attach to remote node: %v", err) 48 | } 49 | 50 | // check net type 51 | _, err = common.StringToChainID(net) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | // get shard id 57 | nodeRPCReply, err := rpc.Request(rpc.Method.GetShardID, node, []interface{}{}) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | shard := int(nodeRPCReply["result"].(float64)) 63 | 64 | config := console.Config{ 65 | DataDir: checkAndMakeDirIfNeeded(), 66 | DocRoot: ".", 67 | Client: client, 68 | Preload: nil, 69 | NodeUrl: node, 70 | ShardId: shard, 71 | Net: net, 72 | } 73 | 74 | consoleInstance, err := console.New(config) 75 | if err != nil { 76 | log.Fatalf("Failed to start the JavaScript console insatnce: %v", err) 77 | } 78 | defer consoleInstance.Stop(false) 79 | 80 | // Otherwise print the welcome screen and enter interactive mode 81 | consoleInstance.Welcome() 82 | consoleInstance.Interactive() 83 | 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/keys/bls_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | func TestBlsKeyGeneration(t *testing.T) { 11 | valid := true 12 | passphrase := "" 13 | folderPath := "TestBlsKeyGeneration" 14 | absFolderPath, err := filepath.Abs(fmt.Sprintf("./%s", folderPath)) 15 | if err != nil { 16 | t.Errorf("TestBlsKeyGeneration - failed to convert relative path to absolute path") 17 | } 18 | 19 | absFilePath := fmt.Sprintf("%s/TestBlsKeyGeneration.key", absFolderPath) 20 | 21 | err = os.MkdirAll(absFolderPath, 0755) 22 | if err != nil { 23 | t.Errorf("TestBlsKeyGeneration - failed to make test key folder") 24 | } 25 | 26 | if err := GenBlsKey(&BlsKey{Passphrase: passphrase, FilePath: absFilePath}); err != nil { 27 | t.Errorf("TestBlsKeyGeneration - failed to generate bls key using passphrase %s and path %s", passphrase, absFilePath) 28 | } 29 | 30 | if _, err = os.Stat(absFilePath); err != nil { 31 | t.Errorf("TestBlsKeyGeneration - failed to check if file %s exists", absFilePath) 32 | } 33 | 34 | valid = !os.IsNotExist(err) 35 | 36 | if !valid { 37 | t.Errorf("GenBlsKey - failed to generate a bls key using passphrase %s", "") 38 | } 39 | 40 | os.RemoveAll(absFolderPath) 41 | } 42 | 43 | func TestMultiBlsKeyGeneration(t *testing.T) { 44 | tests := []struct { 45 | node string 46 | count uint32 47 | shardID uint32 48 | filePath string 49 | expected bool 50 | }{ 51 | {node: "https://api.s0.b.hmny.io", count: 3, shardID: 0, expected: true}, 52 | {node: "https://api.s0.b.hmny.io", count: 3, shardID: 4, expected: false}, 53 | } 54 | 55 | for _, test := range tests { 56 | valid := false 57 | blsKeys := []*BlsKey{} 58 | for i := uint32(0); i < test.count; i++ { 59 | blsKeys = append(blsKeys, &BlsKey{Passphrase: "", FilePath: ""}) 60 | } 61 | 62 | blsKeys, shardCount, err := genBlsKeyForNode(blsKeys, test.node, test.shardID) 63 | if err != nil { 64 | valid = false 65 | } 66 | 67 | successCount := 0 68 | 69 | for _, blsKey := range blsKeys { 70 | success := (blsKey.ShardPublicKey != nil && blsKeyMatchesShardID(blsKey.ShardPublicKey, test.shardID, shardCount)) 71 | if success { 72 | successCount++ 73 | } 74 | } 75 | 76 | valid = (successCount == int(test.count)) 77 | 78 | if valid != test.expected { 79 | t.Errorf("genBlsKeyForNode - failed to generate %d keys for shard %d using node %s", test.count, test.shardID, test.node) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /pkg/console/jsre/completion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package jsre 18 | 19 | import ( 20 | "os" 21 | "reflect" 22 | "testing" 23 | ) 24 | 25 | func TestCompleteKeywords(t *testing.T) { 26 | re := New("", os.Stdout) 27 | re.Run(` 28 | function theClass() { 29 | this.foo = 3; 30 | this.gazonk = {xyz: 4}; 31 | } 32 | theClass.prototype.someMethod = function () {}; 33 | var x = new theClass(); 34 | var y = new theClass(); 35 | y.someMethod = function override() {}; 36 | `) 37 | 38 | var tests = []struct { 39 | input string 40 | want []string 41 | }{ 42 | { 43 | input: "St", 44 | want: []string{"String"}, 45 | }, 46 | { 47 | input: "x", 48 | want: []string{"x."}, 49 | }, 50 | { 51 | input: "x.someMethod", 52 | want: []string{"x.someMethod("}, 53 | }, 54 | { 55 | input: "x.", 56 | want: []string{ 57 | "x.constructor", 58 | "x.foo", 59 | "x.gazonk", 60 | "x.someMethod", 61 | }, 62 | }, 63 | { 64 | input: "y.", 65 | want: []string{ 66 | "y.constructor", 67 | "y.foo", 68 | "y.gazonk", 69 | "y.someMethod", 70 | }, 71 | }, 72 | { 73 | input: "x.gazonk.", 74 | want: []string{ 75 | "x.gazonk.constructor", 76 | "x.gazonk.hasOwnProperty", 77 | "x.gazonk.isPrototypeOf", 78 | "x.gazonk.propertyIsEnumerable", 79 | "x.gazonk.toLocaleString", 80 | "x.gazonk.toString", 81 | "x.gazonk.valueOf", 82 | "x.gazonk.xyz", 83 | }, 84 | }, 85 | } 86 | for _, test := range tests { 87 | cs := re.CompleteKeywords(test.input) 88 | if !reflect.DeepEqual(cs, test.want) { 89 | t.Errorf("wrong completions for %q\ngot %v\nwant %v", test.input, cs, test.want) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pkg/rpc/query.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/valyala/fasthttp" 10 | 11 | "github.com/harmony-one/go-sdk/pkg/common" 12 | ) 13 | 14 | var ( 15 | queryID = 0 16 | post = []byte("POST") 17 | ) 18 | 19 | func baseRequest(method string, node string, params interface{}) ([]byte, error) { 20 | requestBody, _ := json.Marshal(map[string]interface{}{ 21 | "jsonrpc": common.JSONRPCVersion, 22 | "id": strconv.Itoa(queryID), 23 | "method": method, 24 | "params": params, 25 | }) 26 | const contentType = "application/json" 27 | req := fasthttp.AcquireRequest() 28 | req.SetBody(requestBody) 29 | req.Header.SetMethodBytes(post) 30 | req.Header.SetContentType(contentType) 31 | req.SetRequestURIBytes([]byte(node)) 32 | res := fasthttp.AcquireResponse() 33 | if err := fasthttp.Do(req, res); err != nil { 34 | return nil, err 35 | } 36 | c := res.StatusCode() 37 | if c != 200 { 38 | return nil, fmt.Errorf("http status code not 200, received: %d", c) 39 | } 40 | fasthttp.ReleaseRequest(req) 41 | body := res.Body() 42 | result := make([]byte, len(body)) 43 | copy(result, body) 44 | fasthttp.ReleaseResponse(res) 45 | if common.DebugRPC { 46 | reqB := common.JSONPrettyFormat(string(requestBody)) 47 | respB := common.JSONPrettyFormat(string(body)) 48 | fmt.Printf("Response Timestamp: %s\n", time.Now().String()) 49 | fmt.Printf("URL: %s, Request Body: %s\n\n", node, reqB) 50 | fmt.Printf("URL: %s, Response Body: %s\n\n", node, respB) 51 | } 52 | queryID++ 53 | return result, nil 54 | } 55 | 56 | // TODO Check if Method known, return error when not known, good intern task 57 | 58 | // Request processes 59 | func Request(method string, node string, params interface{}) (Reply, error) { 60 | rpcJSON := make(map[string]interface{}) 61 | rawReply, err := baseRequest(method, node, params) 62 | if err != nil { 63 | return nil, err 64 | } 65 | json.Unmarshal(rawReply, &rpcJSON) 66 | if oops := rpcJSON["error"]; oops != nil { 67 | errNo := oops.(map[string]interface{})["code"].(float64) 68 | errMessage := "" 69 | if oops.(map[string]interface{})["message"] != nil { 70 | errMessage = oops.(map[string]interface{})["message"].(string) 71 | } 72 | return nil, ErrorCodeToError(errMessage, errNo) 73 | } 74 | return rpcJSON, nil 75 | } 76 | 77 | // RawRequest is to sidestep the lifting done by Request 78 | func RawRequest(method string, node string, params interface{}) ([]byte, error) { 79 | return baseRequest(method, node, params) 80 | } 81 | -------------------------------------------------------------------------------- /pkg/governance/signing_test.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "encoding/hex" 5 | "os" 6 | "path" 7 | "testing" 8 | 9 | "github.com/btcsuite/btcd/btcec" 10 | "github.com/harmony-one/go-sdk/pkg/common" 11 | "github.com/harmony-one/harmony/accounts" 12 | ) 13 | 14 | func TestSigning(t *testing.T) { 15 | // make the EIP712 data structure 16 | vote := Vote{ 17 | Space: "yam.eth", 18 | Proposal: "0x21ea31e896ec5b5a49a3653e51e787ee834aaf953263144ab936ed756f36609f", 19 | ProposalType: "single-choice", 20 | Choice: "1", 21 | App: "my-app", 22 | Timestamp: 1660909056, 23 | From: "0x9E713963a92c02317A681b9bB3065a8249DE124F", 24 | } 25 | typedData, err := vote.ToEIP712() 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | // add a temporary key store with the below private key 31 | location := path.Join(os.TempDir(), "hmy-test") 32 | keyStore := common.KeyStoreForPath(location) 33 | privateKeyBytes, _ := hex.DecodeString("91c8360c4cb4b5fac45513a7213f31d4e4a7bfcb4630e9fbf074f42a203ac0b9") 34 | sk, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) 35 | passphrase := "" 36 | keyStore.ImportECDSA(sk.ToECDSA(), passphrase) 37 | keyStore.Unlock(accounts.Account{Address: keyStore.Accounts()[0].Address}, passphrase) 38 | account := accounts.Account{ 39 | Address: keyStore.Accounts()[0].Address, 40 | } 41 | 42 | sign, err := signTypedData(keyStore, account, typedData) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | expectedSig := "0x6b572bacbb44efe75cad5b938a5d4fe64c3495bec28807e78989e3159e11d21d5d5568ffabcf274194830b6cb375355af423995afc7ee290e4a632b12bdbe0cc1b" 47 | if sign != expectedSig { 48 | t.Errorf("invalid sig: got %s but expected %s", sign, expectedSig) 49 | } 50 | 51 | os.RemoveAll(location) 52 | } 53 | 54 | // The below NodeJS code was used to generate the above signature 55 | // import snapshot from '@snapshot-labs/snapshot.js'; 56 | // import { Wallet } from "ethers"; 57 | // const hub = 'https://hub.snapshot.org'; 58 | // const client = new snapshot.Client712(hub); 59 | // const wallet = new Wallet("91c8360c4cb4b5fac45513a7213f31d4e4a7bfcb4630e9fbf074f42a203ac0b9"); 60 | // const receipt = await client.vote(wallet, await wallet.getAddress(), { 61 | // space: 'yam.eth', 62 | // proposal: '0x21ea31e896ec5b5a49a3653e51e787ee834aaf953263144ab936ed756f36609f', 63 | // type: 'single-choice', 64 | // choice: 1, 65 | // app: 'my-app', 66 | // timestamp: 1660909056, 67 | // }); 68 | 69 | // package.json 70 | // "@snapshot-labs/snapshot.js": "^0.4.18" 71 | // "ethers": "^5.6.9" 72 | -------------------------------------------------------------------------------- /cmd/subcommands/validator.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/harmony-one/go-sdk/pkg/rpc" 8 | "github.com/pkg/errors" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ( 13 | validatorSubCmds = []*cobra.Command{{ 14 | Use: "elected", 15 | Short: "elected validators", 16 | RunE: func(cmd *cobra.Command, args []string) error { 17 | noLatest = true 18 | return request(rpc.Method.GetElectedValidatorAddresses, []interface{}{}) 19 | }, 20 | }, { 21 | Use: "all", 22 | Short: "all validator addresses", 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | return request(rpc.Method.GetAllValidatorAddresses, []interface{}{}) 25 | }, 26 | }, { 27 | Use: "information", 28 | Short: "original creation record of a validator", 29 | Args: cobra.ExactArgs(1), 30 | PreRunE: validateAddress, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | noLatest = true 33 | e := request(rpc.Method.GetValidatorInformation, []interface{}{addr.address}) 34 | if e != nil { 35 | return fmt.Errorf("validator address not found: %s", addr.address) 36 | } 37 | return e 38 | }, 39 | }, { 40 | Use: "information-by-block-number", 41 | Short: "original creation record of a validator by block number", 42 | Args: cobra.ExactArgs(2), 43 | PreRunE: validateAddress, 44 | RunE: func(cmd *cobra.Command, args []string) error { 45 | noLatest = true 46 | return request(rpc.Method.GetValidatorInformationByBlockNumber, []interface{}{addr.address, args[1]}) 47 | }, 48 | }, { 49 | Use: "all-information", 50 | Short: "all validators information", 51 | Args: cobra.ExactArgs(1), 52 | RunE: func(cmd *cobra.Command, args []string) error { 53 | noLatest = true 54 | page, err := strconv.ParseInt(args[0], 10, 64) 55 | if err != nil { 56 | return errors.Wrapf(err, "the page argument must be integer, supplied %v", args[0]) 57 | } 58 | return request(rpc.Method.GetAllValidatorInformation, []interface{}{page}) 59 | }, 60 | }, { 61 | Use: "all-information-by-block-number", 62 | Short: "all validators information by block number", 63 | Args: cobra.ExactArgs(2), 64 | RunE: func(cmd *cobra.Command, args []string) error { 65 | noLatest = true 66 | page, err := strconv.ParseInt(args[0], 10, 64) 67 | if err != nil { 68 | return errors.Wrapf(err, "the page argument must be integer, supplied %v", args[0]) 69 | } 70 | return request(rpc.Method.GetAllValidatorInformationByBlockNumber, []interface{}{page, args[1]}) 71 | }, 72 | }, 73 | } 74 | ) 75 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | TOP:=$(realpath ..) 3 | export LD_LIBRARY_PATH:=$(TOP)/bls/lib:$(TOP)/mcl/lib:/usr/local/opt/openssl/lib:/opt/homebrew/opt/gmp/lib/:/opt/homebrew/opt/openssl/lib 4 | export LIBRARY_PATH:=$(LD_LIBRARY_PATH) 5 | version := $(shell git rev-list --count HEAD) 6 | commit := $(shell git describe --always --long --dirty) 7 | built_at := $(shell date +%FT%T%z) 8 | built_by := ${USER}@harmony.one 9 | 10 | flags := -gcflags="all=-N -l" 11 | ldflags := -X main.version=v${version} -X main.commit=${commit} 12 | ldflags += -X main.builtAt=${built_at} -X main.builtBy=${built_by} 13 | cli := ./dist/hmy 14 | upload-path-darwin := 's3://pub.harmony.one/release/darwin-x86_64/mainnet/hmy' 15 | upload-path-linux := 's3://pub.harmony.one/release/linux-x86_64/mainnet/hmy' 16 | upload-path-linux-version := 's3://pub.harmony.one/release/linux-x86_64/mainnet/hmy_version' 17 | uname := $(shell uname) 18 | GOPATH := $(shell go env GOPATH) 19 | 20 | env := GO111MODULE=on 21 | 22 | DIR := ${CURDIR} 23 | export CGO_LDFLAGS=-L$(DIR)/dist/lib -Wl,-rpath -Wl,\$ORIGIN/lib 24 | 25 | all: 26 | source ${GOPATH}/src/github.com/harmony-one/harmony/scripts/setup_bls_build_flags.sh && $(env) go build -o $(cli) -ldflags="$(ldflags)" cmd/main.go 27 | cp $(cli) hmy 28 | 29 | static: 30 | make -C ${GOPATH}/src/github.com/harmony-one/mcl 31 | make -C ${GOPATH}/src/github.com/harmony-one/bls minimised_static BLS_SWAP_G=1 32 | source ${GOPATH}/src/github.com/harmony-one/harmony/scripts/setup_bls_build_flags.sh && $(env) go build -o $(cli) -ldflags="$(ldflags) -w -extldflags \"-static\"" cmd/main.go 33 | cp $(cli) hmy 34 | 35 | debug: 36 | source ${GOPATH}/src/github.com/harmony-one/harmony/scripts/setup_bls_build_flags.sh && $(env) go build $(flags) -o $(cli) -ldflags="$(ldflags)" cmd/main.go 37 | cp $(cli) hmy 38 | 39 | install:all 40 | cp $(cli) ~/.local/bin 41 | 42 | run-tests: test-rpc test-key; 43 | 44 | test-key: 45 | go test ./pkg/keys -cover -v 46 | 47 | test-rpc: 48 | go test ./pkg/rpc -cover -v 49 | 50 | # Notice assumes you have correct uploading credentials 51 | upload-darwin:all 52 | ifeq (${uname}, Darwin) 53 | aws --profile upload s3 cp ./hmy ${upload-path-darwin} 54 | else 55 | @echo "Wrong operating system for target upload" 56 | endif 57 | 58 | # Only the linux build will upload the CLI version 59 | upload-linux:static 60 | ifeq (${uname}, Linux) 61 | aws --profile upload s3 cp ./hmy ${upload-path-linux} 62 | ./hmy version &> ./hmy_version 63 | aws --profile upload s3 cp ./hmy_version ${upload-path-linux-version} 64 | else 65 | @echo "Wrong operating system for target upload" 66 | endif 67 | 68 | .PHONY:clean run-tests upload-darwin upload-linux 69 | 70 | clean: 71 | @rm -f $(cli) 72 | @rm -rf ./dist 73 | -------------------------------------------------------------------------------- /pkg/address/address.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/btcsuite/btcutil/bech32" 5 | ethCommon "github.com/ethereum/go-ethereum/common" 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | const ( 10 | // HashLength is the expected length of the hash 11 | HashLength = ethCommon.HashLength 12 | // AddressLength is the expected length of the address 13 | AddressLength = ethCommon.AddressLength 14 | Bech32AddressHRP = "one" 15 | ) 16 | 17 | type T = ethCommon.Address 18 | 19 | func decodeAndConvert(bech string) (string, []byte, error) { 20 | hrp, data, err := bech32.Decode(bech) 21 | if err != nil { 22 | return "", nil, errors.Wrap(err, "decoding bech32 failed") 23 | } 24 | converted, err := bech32.ConvertBits(data, 5, 8, false) 25 | if err != nil { 26 | return "", nil, errors.Wrap(err, "decoding bech32 failed") 27 | } 28 | return hrp, converted, nil 29 | } 30 | 31 | // TODO ek – the following functions use Ethereum addresses until we have a 32 | // proper abstraction set in place. 33 | 34 | // ParseBech32Addr decodes the given bech32 address and populates the given 35 | // human-readable-part string and address with the decoded result. 36 | func parseBech32Addr(b32 string, hrp *string, addr *T) error { 37 | h, b, err := decodeAndConvert(b32) 38 | if err != nil { 39 | return errors.Wrapf(err, "cannot decode %#v as bech32 address", b32) 40 | } 41 | if len(b) != AddressLength { 42 | return errors.Errorf("decoded bech32 %#v has invalid length %d", 43 | b32, len(b)) 44 | } 45 | *hrp = h 46 | addr.SetBytes(b) 47 | return nil 48 | } 49 | 50 | func Bech32ToAddress(b32 string) (addr T, err error) { 51 | var hrp string 52 | err = parseBech32Addr(b32, &hrp, &addr) 53 | if err == nil && hrp != Bech32AddressHRP { 54 | err = errors.Errorf("%#v is not a %#v address", b32, Bech32AddressHRP) 55 | } 56 | return 57 | } 58 | 59 | // ParseAddr parses the given address, either as bech32 or as hex. 60 | // The result can be 0x00..00 if the passing param is not a correct address. 61 | func Parse(s string) T { 62 | if addr, err := Bech32ToAddress(s); err == nil { 63 | return addr 64 | } 65 | // The result can be 0x00...00 if the passing param is not a correct address. 66 | return ethCommon.HexToAddress(s) 67 | } 68 | 69 | func ToBech32(addr T) string { 70 | b32, err := BuildBech32Addr(Bech32AddressHRP, addr) 71 | if err != nil { 72 | panic(err) 73 | } 74 | return b32 75 | } 76 | 77 | func BuildBech32Addr(hrp string, addr T) (string, error) { 78 | return ConvertAndEncode(hrp, addr.Bytes()) 79 | } 80 | 81 | func ConvertAndEncode(hrp string, data []byte) (string, error) { 82 | converted, err := bech32.ConvertBits(data, 8, 5, true) 83 | if err != nil { 84 | return "", errors.Wrap(err, "encoding bech32 failed") 85 | } 86 | return bech32.Encode(hrp, converted) 87 | 88 | } 89 | -------------------------------------------------------------------------------- /pkg/console/jsre/completion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package jsre 18 | 19 | import ( 20 | "sort" 21 | "strings" 22 | 23 | "github.com/dop251/goja" 24 | ) 25 | 26 | // CompleteKeywords returns potential continuations for the given line. Since line is 27 | // evaluated, callers need to make sure that evaluating line does not have side effects. 28 | func (jsre *JSRE) CompleteKeywords(line string) []string { 29 | var results []string 30 | jsre.Do(func(vm *goja.Runtime) { 31 | results = getCompletions(vm, line) 32 | }) 33 | return results 34 | } 35 | 36 | func getCompletions(vm *goja.Runtime, line string) (results []string) { 37 | parts := strings.Split(line, ".") 38 | if len(parts) == 0 { 39 | return nil 40 | } 41 | 42 | // Find the right-most fully named object in the line. e.g. if line = "x.y.z" 43 | // and "x.y" is an object, obj will reference "x.y". 44 | obj := vm.GlobalObject() 45 | for i := 0; i < len(parts)-1; i++ { 46 | v := obj.Get(parts[i]) 47 | if v == nil { 48 | return nil // No object was found 49 | } 50 | obj = v.ToObject(vm) 51 | } 52 | 53 | // Go over the keys of the object and retain the keys matching prefix. 54 | // Example: if line = "x.y.z" and "x.y" exists and has keys "zebu", "zebra" 55 | // and "platypus", then "x.y.zebu" and "x.y.zebra" will be added to results. 56 | prefix := parts[len(parts)-1] 57 | iterOwnAndConstructorKeys(vm, obj, func(k string) { 58 | if strings.HasPrefix(k, prefix) { 59 | if len(parts) == 1 { 60 | results = append(results, k) 61 | } else { 62 | results = append(results, strings.Join(parts[:len(parts)-1], ".")+"."+k) 63 | } 64 | } 65 | }) 66 | 67 | // Append opening parenthesis (for functions) or dot (for objects) 68 | // if the line itself is the only completion. 69 | if len(results) == 1 && results[0] == line { 70 | obj := obj.Get(parts[len(parts)-1]) 71 | if obj != nil { 72 | if _, isfunc := goja.AssertFunction(obj); isfunc { 73 | results[0] += "(" 74 | } else { 75 | results[0] += "." 76 | } 77 | } 78 | } 79 | 80 | sort.Strings(results) 81 | return results 82 | } 83 | -------------------------------------------------------------------------------- /cmd/subcommands/governance.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/governance" 7 | "github.com/harmony-one/go-sdk/pkg/store" 8 | "github.com/harmony-one/harmony/accounts" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func init() { 13 | cmdGovernance := &cobra.Command{ 14 | Use: "governance", 15 | Short: "Interact with the Harmony spaces on https://snapshot.org", 16 | Long: ` 17 | Support interaction with the Harmony governance space on Snapshot, especially for validators that do not want to import their account private key into either metamask or onewallet. 18 | `, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | cmd.Help() 21 | return nil 22 | }, 23 | } 24 | 25 | cmdGovernance.AddCommand([]*cobra.Command{ 26 | commandVote(), 27 | }...) 28 | 29 | RootCmd.AddCommand(cmdGovernance) 30 | } 31 | 32 | func commandVote() (cmd *cobra.Command) { 33 | var space string 34 | var proposal string 35 | var choice string 36 | var key string 37 | var proposalType string 38 | // var privacy string 39 | var app string 40 | var reason string 41 | 42 | cmd = &cobra.Command{ 43 | Use: "vote-proposal", 44 | Short: "Vote on a proposal", 45 | RunE: func(cmd *cobra.Command, args []string) error { 46 | keyStore := store.FromAccountName(key) 47 | passphrase, err := getPassphrase() 48 | if err != nil { 49 | return err 50 | } 51 | 52 | if len(keyStore.Accounts()) <= 0 { 53 | return fmt.Errorf("couldn't find address from the key") 54 | } 55 | 56 | account := accounts.Account{Address: keyStore.Accounts()[0].Address} 57 | err = keyStore.Unlock(accounts.Account{Address: keyStore.Accounts()[0].Address}, passphrase) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return governance.DoVote(keyStore, account, governance.Vote{ 63 | Space: space, 64 | Proposal: proposal, 65 | ProposalType: proposalType, 66 | Choice: choice, 67 | // Privacy: privacy, 68 | App: app, 69 | From: account.Address.Hex(), 70 | Reason: reason, 71 | }) 72 | }, 73 | } 74 | 75 | cmd.Flags().StringVar(&key, "key", "", "Account name. Must first use (hmy keys import-private-key) to import.") 76 | cmd.Flags().StringVar(&space, "space", "harmony-mainnet.eth", "Snapshot space") 77 | cmd.Flags().StringVar(&proposal, "proposal", "", "Proposal hash") 78 | cmd.Flags().StringVar(&proposalType, "proposal-type", "single-choice", "Proposal type like single-choice, approval, quadratic, etc.") 79 | cmd.Flags().StringVar(&choice, "choice", "", "Vote choice either as integer, list of integers (e.x. when using ranked choice voting), or string") 80 | // cmd.Flags().StringVar(&privacy, "privacy", "", "Vote privacy e.x. shutter") 81 | cmd.Flags().StringVar(&app, "app", "snapshot", "Voting app") 82 | cmd.Flags().StringVar(&reason, "reason", "", "Reason for your choice") 83 | cmd.Flags().BoolVar(&userProvidesPassphrase, "passphrase", false, ppPrompt) 84 | 85 | cmd.MarkFlagRequired("key") 86 | cmd.MarkFlagRequired("proposal") 87 | cmd.MarkFlagRequired("choice") 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /pkg/rpc/common/methods.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type RpcMethod = string 4 | 5 | type RpcEnumList struct { 6 | GetShardingStructure RpcMethod 7 | GetBlockByHash RpcMethod 8 | GetBlockByNumber RpcMethod 9 | GetBlockTransactionCountByHash RpcMethod 10 | GetBlockTransactionCountByNumber RpcMethod 11 | GetCode RpcMethod 12 | GetTransactionByBlockHashAndIndex RpcMethod 13 | GetTransactionByBlockNumberAndIndex RpcMethod 14 | GetTransactionByHash RpcMethod 15 | GetStakingTransactionByHash RpcMethod 16 | GetTransactionReceipt RpcMethod 17 | Syncing RpcMethod 18 | PeerCount RpcMethod 19 | GetBalance RpcMethod 20 | GetStorageAt RpcMethod 21 | GetTransactionCount RpcMethod 22 | SendTransaction RpcMethod 23 | SendRawTransaction RpcMethod 24 | Subscribe RpcMethod 25 | GetPastLogs RpcMethod 26 | GetWork RpcMethod 27 | GetProof RpcMethod 28 | GetFilterChanges RpcMethod 29 | NewPendingTransactionFilter RpcMethod 30 | NewBlockFilter RpcMethod 31 | NewFilter RpcMethod 32 | Call RpcMethod 33 | EstimateGas RpcMethod 34 | GasPrice RpcMethod 35 | BlockNumber RpcMethod 36 | UnSubscribe RpcMethod 37 | NetVersion RpcMethod 38 | ProtocolVersion RpcMethod 39 | GetNodeMetadata RpcMethod 40 | GetLatestBlockHeader RpcMethod 41 | SendRawStakingTransaction RpcMethod 42 | GetElectedValidatorAddresses RpcMethod 43 | GetAllValidatorAddresses RpcMethod 44 | GetValidatorInformation RpcMethod 45 | GetAllValidatorInformation RpcMethod 46 | GetValidatorInformationByBlockNumber RpcMethod 47 | GetAllValidatorInformationByBlockNumber RpcMethod 48 | GetDelegationsByDelegator RpcMethod 49 | GetDelegationsByValidator RpcMethod 50 | GetCurrentTransactionErrorSink RpcMethod 51 | GetMedianRawStakeSnapshot RpcMethod 52 | GetCurrentStakingErrorSink RpcMethod 53 | GetTransactionsHistory RpcMethod 54 | GetPendingTxnsInPool RpcMethod 55 | GetPendingCrosslinks RpcMethod 56 | GetPendingCXReceipts RpcMethod 57 | GetCurrentUtilityMetrics RpcMethod 58 | ResendCX RpcMethod 59 | GetSuperCommmittees RpcMethod 60 | GetCurrentBadBlocks RpcMethod 61 | GetShardID RpcMethod 62 | GetLastCrossLinks RpcMethod 63 | GetLatestChainHeaders RpcMethod 64 | } 65 | 66 | // ValidatedMethod checks if given method is known 67 | func ValidatedMethod(m RpcMethod) string { 68 | switch m := RpcMethod(m); m { 69 | default: 70 | return string(m) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.github/workflows/test-build.yml: -------------------------------------------------------------------------------- 1 | name: test build 2 | 3 | on: 4 | push: 5 | 6 | env: 7 | GOPATH: ${{ github.workspace }} 8 | GOBIN: ${{ github.workspace }}/bin 9 | 10 | jobs: 11 | build-x86_64: 12 | name: Build hmy binary for x86_64 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: [ ubuntu-22.04, macos-latest ] 17 | 18 | steps: 19 | - name: Checkout hmy code 20 | uses: actions/checkout@v4 21 | with: 22 | path: go-sdk 23 | 24 | - name: Set up Go 25 | uses: actions/setup-go@v4 26 | with: 27 | go-version-file: go-sdk/go.mod 28 | 29 | - name: Checkout dependence repo 30 | uses: actions/checkout@v4 31 | with: 32 | repository: harmony-one/mcl 33 | path: ${{ github.workspace }}/src/github.com/harmony-one/mcl 34 | ref: master 35 | fetch-depth: 0 36 | 37 | - name: Checkout dependence repo 38 | uses: actions/checkout@v4 39 | with: 40 | repository: harmony-one/bls 41 | path: ${{ github.workspace }}/src/github.com/harmony-one/bls 42 | ref: master 43 | fetch-depth: 0 44 | 45 | - name: Checkout dependence code 46 | uses: actions/checkout@v4 47 | with: 48 | repository: harmony-one/harmony 49 | path: ${{ github.workspace }}/src/github.com/harmony-one/harmony 50 | ref: main 51 | fetch-depth: 0 52 | 53 | - name: Get latest version and release 54 | run: | 55 | VERSION=$(git tag -l --sort=-v:refname | head -n 1 | tr -d v) 56 | RELEASE=$(git describe --long | cut -f2 -d-) 57 | echo "build_version=$VERSION" >> $GITHUB_ENV 58 | echo "build_release=$RELEASE" >> $GITHUB_ENV 59 | working-directory: go-sdk 60 | 61 | - name: Debug 62 | run: | 63 | pwd 64 | echo ${HOME} 65 | echo ${GITHUB_WORKSPACE} 66 | echo ${GOPATH} 67 | echo ${GOROOT} 68 | ls ${{ github.workspace }}/src/github.com/harmony-one/ 69 | 70 | - name: Build hmy binary for linux ubuntu 71 | if: matrix.os == 'ubuntu-22.04' 72 | run: | 73 | make static 74 | working-directory: go-sdk 75 | 76 | - name: Build libs for macos-latest 77 | if: matrix.os == 'macos-latest' 78 | run: | 79 | brew install gmp 80 | brew install openssl 81 | sudo mkdir -p /opt/homebrew/opt/ 82 | sudo ln -sf /usr/local/opt/openssl@1.1 /opt/homebrew/opt/openssl@1.1 83 | echo "ls -l /opt/homebrew/opt/openssl@1.1"; ls -l /opt/homebrew/opt/openssl@1.1 84 | make libs 85 | working-directory: ${{ github.workspace }}/src/github.com/harmony-one/harmony 86 | 87 | - name: Build hmy binary for macos-latest x86_64 88 | if: matrix.os == 'macos-latest' 89 | run: | 90 | make all 91 | working-directory: go-sdk 92 | 93 | - name: Upload artifact for linux 94 | uses: actions/upload-artifact@v4 95 | if: matrix.os == 'ubuntu-22.04' 96 | with: 97 | name: hmy-linux 98 | path: ${{ github.workspace }}/go-sdk/dist/* 99 | retention-days: 1 100 | 101 | - name: Upload artifact for darwin 102 | uses: actions/upload-artifact@v4 103 | if: matrix.os == 'macos-latest' 104 | with: 105 | name: hmy-darwin 106 | path: ${{ github.workspace }}/go-sdk/dist/* 107 | retention-days: 1 108 | -------------------------------------------------------------------------------- /pkg/console/jsre/jsre_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package jsre 18 | 19 | import ( 20 | "io/ioutil" 21 | "os" 22 | "path" 23 | "reflect" 24 | "testing" 25 | "time" 26 | 27 | "github.com/dop251/goja" 28 | ) 29 | 30 | type testNativeObjectBinding struct { 31 | vm *goja.Runtime 32 | } 33 | 34 | type msg struct { 35 | Msg string 36 | } 37 | 38 | func (no *testNativeObjectBinding) TestMethod(call goja.FunctionCall) goja.Value { 39 | m := call.Argument(0).ToString().String() 40 | return no.vm.ToValue(&msg{m}) 41 | } 42 | 43 | func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) { 44 | dir, err := ioutil.TempDir("", "jsre-test") 45 | if err != nil { 46 | t.Fatal("cannot create temporary directory:", err) 47 | } 48 | if testjs != "" { 49 | if err := ioutil.WriteFile(path.Join(dir, "test.js"), []byte(testjs), os.ModePerm); err != nil { 50 | t.Fatal("cannot create test.js:", err) 51 | } 52 | } 53 | jsre := New(dir, os.Stdout) 54 | return jsre, dir 55 | } 56 | 57 | func TestExec(t *testing.T) { 58 | jsre, dir := newWithTestJS(t, `msg = "testMsg"`) 59 | defer os.RemoveAll(dir) 60 | 61 | err := jsre.Exec("test.js") 62 | if err != nil { 63 | t.Errorf("expected no error, got %v", err) 64 | } 65 | val, err := jsre.Run("msg") 66 | if err != nil { 67 | t.Errorf("expected no error, got %v", err) 68 | } 69 | if val.ExportType().Kind() != reflect.String { 70 | t.Errorf("expected string value, got %v", val) 71 | } 72 | exp := "testMsg" 73 | got := val.ToString().String() 74 | if exp != got { 75 | t.Errorf("expected '%v', got '%v'", exp, got) 76 | } 77 | jsre.Stop(false) 78 | } 79 | 80 | func TestNatto(t *testing.T) { 81 | jsre, dir := newWithTestJS(t, `setTimeout(function(){msg = "testMsg"}, 1);`) 82 | defer os.RemoveAll(dir) 83 | 84 | err := jsre.Exec("test.js") 85 | if err != nil { 86 | t.Errorf("expected no error, got %v", err) 87 | } 88 | time.Sleep(100 * time.Millisecond) 89 | val, err := jsre.Run("msg") 90 | if err != nil { 91 | t.Errorf("expected no error, got %v", err) 92 | } 93 | if val.ExportType().Kind() != reflect.String { 94 | t.Errorf("expected string value, got %v", val) 95 | } 96 | exp := "testMsg" 97 | got := val.ToString().String() 98 | if exp != got { 99 | t.Errorf("expected '%v', got '%v'", exp, got) 100 | } 101 | jsre.Stop(false) 102 | } 103 | 104 | func TestBind(t *testing.T) { 105 | jsre := New("", os.Stdout) 106 | defer jsre.Stop(false) 107 | 108 | jsre.Set("no", &testNativeObjectBinding{vm: jsre.vm}) 109 | 110 | _, err := jsre.Run(`no.TestMethod("testMsg")`) 111 | if err != nil { 112 | t.Errorf("expected no error, got %v", err) 113 | } 114 | } 115 | 116 | func TestLoadScript(t *testing.T) { 117 | jsre, dir := newWithTestJS(t, `msg = "testMsg"`) 118 | defer os.RemoveAll(dir) 119 | 120 | _, err := jsre.Run(`loadScript("test.js")`) 121 | if err != nil { 122 | t.Errorf("expected no error, got %v", err) 123 | } 124 | val, err := jsre.Run("msg") 125 | if err != nil { 126 | t.Errorf("expected no error, got %v", err) 127 | } 128 | if val.ExportType().Kind() != reflect.String { 129 | t.Errorf("expected string value, got %v", val) 130 | } 131 | exp := "testMsg" 132 | got := val.ToString().String() 133 | if exp != got { 134 | t.Errorf("expected '%v', got '%v'", exp, got) 135 | } 136 | jsre.Stop(false) 137 | } 138 | -------------------------------------------------------------------------------- /pkg/account/import.go: -------------------------------------------------------------------------------- 1 | package account 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum/crypto" 7 | "github.com/mitchellh/go-homedir" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "github.com/btcsuite/btcd/btcec" 15 | mapset "github.com/deckarep/golang-set" 16 | "github.com/harmony-one/go-sdk/pkg/address" 17 | "github.com/harmony-one/go-sdk/pkg/common" 18 | "github.com/harmony-one/go-sdk/pkg/mnemonic" 19 | "github.com/harmony-one/go-sdk/pkg/store" 20 | "github.com/harmony-one/harmony/accounts/keystore" 21 | ) 22 | 23 | // ImportFromPrivateKey allows import of an ECDSA private key 24 | func ImportFromPrivateKey(privateKey, name, passphrase string) (string, error) { 25 | privateKey = strings.TrimPrefix(privateKey, "0x") 26 | 27 | if name == "" { 28 | name = generateName() + "-imported" 29 | for store.DoesNamedAccountExist(name) { 30 | name = generateName() + "-imported" 31 | } 32 | } else if store.DoesNamedAccountExist(name) { 33 | return "", fmt.Errorf("account %s already exists", name) 34 | } 35 | 36 | privateKeyBytes, err := hex.DecodeString(privateKey) 37 | if err != nil { 38 | return "", err 39 | } 40 | if len(privateKeyBytes) != common.Secp256k1PrivateKeyBytesLength { 41 | return "", common.ErrBadKeyLength 42 | } 43 | 44 | // btcec.PrivKeyFromBytes only returns a secret key and public key 45 | sk, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes) 46 | oneAddress := address.ToBech32(crypto.PubkeyToAddress(sk.PublicKey)) 47 | 48 | if store.FromAddress(oneAddress) != nil { 49 | return "", fmt.Errorf("address %s already exists", oneAddress) 50 | } 51 | 52 | ks := store.FromAccountName(name) 53 | _, err = ks.ImportECDSA(sk.ToECDSA(), passphrase) 54 | return name, err 55 | } 56 | 57 | func generateName() string { 58 | words := strings.Split(mnemonic.Generate(), " ") 59 | existingAccounts := mapset.NewSet() 60 | for a := range store.LocalAccounts() { 61 | existingAccounts.Add(a) 62 | } 63 | foundName := false 64 | acct := "" 65 | i := 0 66 | for { 67 | if foundName { 68 | break 69 | } 70 | if i == len(words)-1 { 71 | words = strings.Split(mnemonic.Generate(), " ") 72 | } 73 | candidate := words[i] 74 | if !existingAccounts.Contains(candidate) { 75 | foundName = true 76 | acct = candidate 77 | break 78 | } 79 | } 80 | return acct 81 | } 82 | 83 | func writeToFile(path string, data string) error { 84 | currDir, _ := os.Getwd() 85 | path, err := filepath.Abs(path) 86 | if err != nil { 87 | return err 88 | } 89 | os.MkdirAll(filepath.Dir(path), 0777) 90 | os.Chdir(filepath.Dir(path)) 91 | file, err := os.Create(filepath.Base(path)) 92 | if err != nil { 93 | return err 94 | } 95 | defer file.Close() 96 | 97 | _, err = io.WriteString(file, data) 98 | if err != nil { 99 | return err 100 | } 101 | os.Chdir(currDir) 102 | return file.Sync() 103 | } 104 | 105 | // ImportKeyStore imports a keystore along with a password 106 | func ImportKeyStore(keyPath, name, passphrase string) (string, error) { 107 | keyPath, err := filepath.Abs(keyPath) 108 | if err != nil { 109 | return "", err 110 | } 111 | keyJSON, readError := ioutil.ReadFile(keyPath) 112 | if readError != nil { 113 | return "", readError 114 | } 115 | if name == "" { 116 | name = generateName() + "-imported" 117 | for store.DoesNamedAccountExist(name) { 118 | name = generateName() + "-imported" 119 | } 120 | } else if store.DoesNamedAccountExist(name) { 121 | return "", fmt.Errorf("account %s already exists", name) 122 | } 123 | key, err := keystore.DecryptKey(keyJSON, passphrase) 124 | if err != nil { 125 | return "", err 126 | } 127 | b32 := address.ToBech32(key.Address) 128 | hasAddress := store.FromAddress(b32) != nil 129 | if hasAddress { 130 | return "", fmt.Errorf("address %s already exists in keystore", b32) 131 | } 132 | uDir, _ := homedir.Dir() 133 | newPath := filepath.Join(uDir, common.DefaultConfigDirName, common.DefaultConfigAccountAliasesDirName, name, filepath.Base(keyPath)) 134 | err = writeToFile(newPath, string(keyJSON)) 135 | if err != nil { 136 | return "", err 137 | } 138 | return name, nil 139 | } 140 | -------------------------------------------------------------------------------- /cmd/subcommands/utility.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/big" 7 | "strings" 8 | 9 | bls_core "github.com/harmony-one/bls/ffi/go/bls" 10 | "github.com/harmony-one/go-sdk/pkg/address" 11 | "github.com/harmony-one/go-sdk/pkg/rpc" 12 | "github.com/harmony-one/harmony/crypto/bls" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | func init() { 17 | cmdUtilities := &cobra.Command{ 18 | Use: "utility", 19 | Short: "common harmony blockchain utilities", 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | cmd.Help() 22 | return nil 23 | }, 24 | } 25 | 26 | cmdUtilities.AddCommand(&cobra.Command{ 27 | Use: "metadata", 28 | Short: "data includes network specific values", 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | noLatest = true 31 | return request(rpc.Method.GetNodeMetadata, []interface{}{}) 32 | }, 33 | }) 34 | 35 | cmdMetrics := &cobra.Command{ 36 | Use: "metrics", 37 | Short: "mostly in-memory fluctuating values", 38 | RunE: func(cmd *cobra.Command, args []string) error { 39 | cmd.Help() 40 | return nil 41 | }, 42 | } 43 | 44 | cmdMetrics.AddCommand([]*cobra.Command{{ 45 | Use: "pending-crosslinks", 46 | Short: "dump the pending crosslinks in memory of target node", 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | noLatest = true 49 | return request(rpc.Method.GetPendingCrosslinks, []interface{}{}) 50 | }, 51 | }, { 52 | Use: "pending-cx-receipts", 53 | Short: "dump the pending cross shard receipts in memory of target node", 54 | RunE: func(cmd *cobra.Command, args []string) error { 55 | noLatest = true 56 | return request(rpc.Method.GetPendingCXReceipts, []interface{}{}) 57 | }, 58 | }, 59 | }...) 60 | 61 | cmdUtilities.AddCommand(cmdMetrics) 62 | cmdUtilities.AddCommand([]*cobra.Command{{ 63 | Use: "bech32-to-addr", 64 | Args: cobra.ExactArgs(1), 65 | Short: "0x Address of a bech32 one-address", 66 | RunE: func(cmd *cobra.Command, args []string) error { 67 | addr, err := address.Bech32ToAddress(args[0]) 68 | if err != nil { 69 | return err 70 | } 71 | fmt.Println(addr.Hex()) 72 | return nil 73 | }, 74 | }, { 75 | Use: "addr-to-bech32", 76 | Args: cobra.ExactArgs(1), 77 | Short: "bech32 one-address of an 0x address", 78 | RunE: func(cmd *cobra.Command, args []string) error { 79 | fmt.Println(address.ToBech32(address.Parse(args[0]))) 80 | return nil 81 | }, 82 | }, { 83 | Use: "committees", 84 | Short: "current and previous committees", 85 | RunE: func(cmd *cobra.Command, args []string) error { 86 | noLatest = true 87 | return request(rpc.Method.GetSuperCommmittees, []interface{}{}) 88 | }, 89 | }, { 90 | Use: "bad-blocks", 91 | Short: "bad blocks in memory of the target node", 92 | RunE: func(cmd *cobra.Command, args []string) error { 93 | noLatest = true 94 | return request(rpc.Method.GetCurrentBadBlocks, []interface{}{}) 95 | }, 96 | }, { 97 | Use: "shards", 98 | Short: "sharding structure and end points", 99 | RunE: func(cmd *cobra.Command, args []string) error { 100 | noLatest = true 101 | return request(rpc.Method.GetShardingStructure, []interface{}{}) 102 | }, 103 | }, { 104 | // Temp utility that should be removed once resharding implemented 105 | Use: "shard-for-bls", 106 | Args: cobra.ExactArgs(1), 107 | Short: "which shard this BLS key would be assigned to", 108 | RunE: func(cmd *cobra.Command, args []string) error { 109 | inputKey := strings.TrimPrefix(args[0], "0x") 110 | key := bls_core.PublicKey{} 111 | if err := key.DeserializeHexStr(inputKey); err != nil { 112 | return err 113 | } 114 | reply, err := rpc.Request(rpc.Method.GetShardingStructure, node, []interface{}{}) 115 | if err != nil { 116 | return err 117 | } 118 | shardBig := len(reply["result"].([]interface{})) // assume the response is a JSON Array 119 | wrapper := bls.FromLibBLSPublicKeyUnsafe(&key) 120 | shardID := int(new(big.Int).Mod(wrapper.Big(), big.NewInt(int64(shardBig))).Int64()) 121 | type t struct { 122 | ShardID int `json:"shard-id"` 123 | } 124 | result, err := json.Marshal(t{shardID}) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | fmt.Println(string(result)) 130 | return nil 131 | }, 132 | }, { 133 | Use: "last-cross-links", 134 | Short: "last crosslinks processed", 135 | RunE: func(cmd *cobra.Command, args []string) error { 136 | noLatest = true 137 | return request(rpc.Method.GetLastCrossLinks, []interface{}{}) 138 | }, 139 | }}...) 140 | 141 | RootCmd.AddCommand(cmdUtilities) 142 | } 143 | -------------------------------------------------------------------------------- /pkg/store/local.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "time" 9 | 10 | "github.com/harmony-one/go-sdk/pkg/address" 11 | "github.com/harmony-one/go-sdk/pkg/common" 12 | c "github.com/harmony-one/go-sdk/pkg/common" 13 | "github.com/harmony-one/harmony/accounts" 14 | "github.com/harmony-one/harmony/accounts/keystore" 15 | "github.com/pkg/errors" 16 | 17 | homedir "github.com/mitchellh/go-homedir" 18 | ) 19 | 20 | func init() { 21 | uDir, _ := homedir.Dir() 22 | hmyCLIDir := path.Join(uDir, common.DefaultConfigDirName, common.DefaultConfigAccountAliasesDirName) 23 | if _, err := os.Stat(hmyCLIDir); os.IsNotExist(err) { 24 | os.MkdirAll(hmyCLIDir, 0700) 25 | } 26 | } 27 | 28 | // LocalAccounts returns a slice of local account alias names 29 | func LocalAccounts() []string { 30 | uDir, _ := homedir.Dir() 31 | files, _ := ioutil.ReadDir(path.Join( 32 | uDir, 33 | common.DefaultConfigDirName, 34 | common.DefaultConfigAccountAliasesDirName, 35 | )) 36 | accounts := []string{} 37 | for _, node := range files { 38 | if node.IsDir() { 39 | accounts = append(accounts, path.Base(node.Name())) 40 | } 41 | } 42 | return accounts 43 | } 44 | 45 | var ( 46 | describe = fmt.Sprintf("%-24s\t\t%23s\n", "NAME", "ADDRESS") 47 | NoUnlockBadPassphrase = errors.New("could not unlock wallet with given passphrase") 48 | ) 49 | 50 | // DescribeLocalAccounts will display all the account alias name and their corresponding one address 51 | func DescribeLocalAccounts() { 52 | fmt.Println(describe) 53 | for _, name := range LocalAccounts() { 54 | ks := FromAccountName(name) 55 | allAccounts := ks.Accounts() 56 | for _, account := range allAccounts { 57 | fmt.Printf("%-48s\t%s\n", name, address.ToBech32(account.Address)) 58 | } 59 | } 60 | } 61 | 62 | // DoesNamedAccountExist return true if the given string name is an alias account already define, 63 | // and return false otherwise 64 | func DoesNamedAccountExist(name string) bool { 65 | for _, account := range LocalAccounts() { 66 | if account == name { 67 | return true 68 | } 69 | } 70 | return false 71 | } 72 | 73 | // Returns one address for account name if exists 74 | func AddressFromAccountName(name string) (string, error) { 75 | ks := FromAccountName(name) 76 | // FIXME: Assume 1 account per keystore for now 77 | for _, account := range ks.Accounts() { 78 | return address.ToBech32(account.Address), nil 79 | } 80 | return "", fmt.Errorf("Keystore not found.") 81 | } 82 | 83 | // FromAddress will return nil if the bech32 string is not found in the imported accounts 84 | func FromAddress(bech32 string) *keystore.KeyStore { 85 | for _, name := range LocalAccounts() { 86 | ks := FromAccountName(name) 87 | allAccounts := ks.Accounts() 88 | for _, account := range allAccounts { 89 | if bech32 == address.ToBech32(account.Address) { 90 | return ks 91 | } 92 | } 93 | } 94 | return nil 95 | } 96 | 97 | func FromAccountName(name string) *keystore.KeyStore { 98 | uDir, _ := homedir.Dir() 99 | p := path.Join(uDir, c.DefaultConfigDirName, c.DefaultConfigAccountAliasesDirName, name) 100 | return common.KeyStoreForPath(p) 101 | } 102 | 103 | func DefaultLocation() string { 104 | uDir, _ := homedir.Dir() 105 | return path.Join(uDir, c.DefaultConfigDirName, c.DefaultConfigAccountAliasesDirName) 106 | } 107 | 108 | func UnlockedKeystore(from, passphrase string) (*keystore.KeyStore, *accounts.Account, error) { 109 | return UnlockedKeystoreTimeLimit(from, passphrase, 0) 110 | } 111 | 112 | func LockKeystore(from string) (*keystore.KeyStore, *accounts.Account, error) { 113 | sender := address.Parse(from) 114 | ks := FromAddress(address.ToBech32(sender)) 115 | if ks == nil { 116 | return nil, nil, fmt.Errorf("could not open local keystore for %s", from) 117 | } 118 | account, lookupErr := ks.Find(accounts.Account{Address: sender}) 119 | if lookupErr != nil { 120 | return nil, nil, fmt.Errorf("could not find %s in keystore", from) 121 | } 122 | if lockError := ks.Lock(account.Address); lockError != nil { 123 | return nil, nil, lockError 124 | } 125 | return ks, &account, nil 126 | } 127 | 128 | func UnlockedKeystoreTimeLimit(from, passphrase string, time time.Duration) (*keystore.KeyStore, *accounts.Account, error) { 129 | sender := address.Parse(from) 130 | ks := FromAddress(address.ToBech32(sender)) 131 | if ks == nil { 132 | return nil, nil, fmt.Errorf("could not open local keystore for %s", from) 133 | } 134 | account, lookupErr := ks.Find(accounts.Account{Address: sender}) 135 | if lookupErr != nil { 136 | return nil, nil, fmt.Errorf("could not find %s in keystore", from) 137 | } 138 | if unlockError := ks.TimedUnlock(account, passphrase, time); unlockError != nil { 139 | return nil, nil, errors.Wrap(NoUnlockBadPassphrase, unlockError.Error()) 140 | } 141 | return ks, &account, nil 142 | } 143 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/harmony-one/go-sdk 2 | 3 | go 1.22.5 4 | 5 | require ( 6 | github.com/btcsuite/btcd v0.22.1 7 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce 8 | github.com/cosmos/cosmos-sdk v0.37.0 9 | github.com/deckarep/golang-set v1.7.1 10 | github.com/dop251/goja v0.0.0-20210427212725-462d53687b0d 11 | github.com/ethereum/go-ethereum v1.9.23 12 | github.com/fatih/color v1.9.0 13 | github.com/harmony-one/bls v0.0.7-0.20191214005344-88c23f91a8a9 14 | github.com/harmony-one/harmony v1.10.2-0.20210123081216-6993b9ad0ca1 15 | github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 16 | github.com/mattn/go-colorable v0.1.9 17 | github.com/mitchellh/go-homedir v1.1.0 18 | github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 19 | github.com/pkg/errors v0.9.1 20 | github.com/spf13/cobra v0.0.5 21 | github.com/tyler-smith/go-bip39 v1.0.2 22 | github.com/valyala/fasthttp v1.2.0 23 | github.com/valyala/fastjson v1.6.3 24 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a 25 | ) 26 | 27 | require ( 28 | github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect 29 | github.com/VictoriaMetrics/fastcache v1.5.7 // indirect 30 | github.com/aristanetworks/goarista v0.0.0-20191023202215-f096da5361bb // indirect 31 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 32 | github.com/cpuguy83/go-md2man v1.0.10 // indirect 33 | github.com/davecgh/go-spew v1.1.1 // indirect 34 | github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect 35 | github.com/edsrzf/mmap-go v1.0.0 // indirect 36 | github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa // indirect 37 | github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect 38 | github.com/go-ole/go-ole v1.2.1 // indirect 39 | github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect 40 | github.com/go-stack/stack v1.8.0 // indirect 41 | github.com/gogo/protobuf v1.3.1 // indirect 42 | github.com/golang/protobuf v1.4.3 // indirect 43 | github.com/golang/snappy v0.0.2-0.20200707131729-196ae77b8a26 // indirect 44 | github.com/google/uuid v1.1.2 // indirect 45 | github.com/gorilla/websocket v1.4.2 // indirect 46 | github.com/harmony-one/taggedrlp v0.1.4 // indirect 47 | github.com/hashicorp/golang-lru v0.5.4 // indirect 48 | github.com/huin/goupnp v1.0.0 // indirect 49 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 50 | github.com/ipfs/go-cid v0.0.7 // indirect 51 | github.com/jackpal/go-nat-pmp v1.0.2 // indirect 52 | github.com/klauspost/compress v1.4.1 // indirect 53 | github.com/klauspost/cpuid v1.2.1 // indirect 54 | github.com/libp2p/go-buffer-pool v0.0.2 // indirect 55 | github.com/libp2p/go-libp2p-core v0.8.0 // indirect 56 | github.com/libp2p/go-openssl v0.0.7 // indirect 57 | github.com/mattn/go-isatty v0.0.14 // indirect 58 | github.com/mattn/go-runewidth v0.0.9 // indirect 59 | github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect 60 | github.com/minio/sha256-simd v0.1.1 // indirect 61 | github.com/mr-tron/base58 v1.2.0 // indirect 62 | github.com/multiformats/go-base32 v0.0.3 // indirect 63 | github.com/multiformats/go-base36 v0.1.0 // indirect 64 | github.com/multiformats/go-multiaddr v0.3.1 // indirect 65 | github.com/multiformats/go-multibase v0.0.3 // indirect 66 | github.com/multiformats/go-multihash v0.0.14 // indirect 67 | github.com/multiformats/go-varint v0.0.6 // indirect 68 | github.com/natefinch/lumberjack v2.0.0+incompatible // indirect 69 | github.com/olekukonko/tablewriter v0.0.5 // indirect 70 | github.com/pborman/uuid v1.2.0 // indirect 71 | github.com/prometheus/tsdb v0.7.1 // indirect 72 | github.com/rjeczalik/notify v0.9.2 // indirect 73 | github.com/rs/cors v1.7.0 // indirect 74 | github.com/rs/zerolog v1.18.0 // indirect 75 | github.com/russross/blackfriday v1.5.2 // indirect 76 | github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect 77 | github.com/spaolacci/murmur3 v1.1.0 // indirect 78 | github.com/spf13/pflag v1.0.5 // indirect 79 | github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 // indirect 80 | github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect 81 | github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect 82 | github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca // indirect 83 | github.com/valyala/bytebufferpool v1.0.0 // indirect 84 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect 85 | golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect 86 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect 87 | golang.org/x/text v0.3.6 // indirect 88 | google.golang.org/protobuf v1.25.0 // indirect 89 | gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect 90 | gopkg.in/yaml.v2 v2.4.0 // indirect 91 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect 92 | ) 93 | 94 | replace github.com/ethereum/go-ethereum => github.com/ethereum/go-ethereum v1.9.9 95 | 96 | replace github.com/fatih/color => github.com/fatih/color v1.13.0 97 | -------------------------------------------------------------------------------- /cmd/subcommands/values.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | ) 8 | 9 | const ( 10 | hmyDocsDir = "hmy-docs" 11 | defaultNodeAddr = "http://localhost:9500" 12 | defaultRpcPrefix = "hmy" 13 | defaultMainnetEndpoint = "https://api.s0.t.hmny.io/" 14 | ) 15 | 16 | var ( 17 | g = color.New(color.FgGreen).SprintFunc() 18 | cookbookDoc = fmt.Sprintf(` 19 | Cookbook of Usage 20 | 21 | Note: 22 | 23 | 1) Every subcommand recognizes a '--help' flag 24 | 2) If a passphrase is used by a subcommand, one can enter their own passphrase interactively 25 | with the --passphrase option. Alternatively, one can pass their own passphrase via a file 26 | using the --passphrase-file option. If no passphrase option is selected, the default 27 | passphrase of '' is used. 28 | 3) These examples use Shard 0 of [NETWORK] as argument for --node 29 | 30 | Examples: 31 | 32 | %s 33 | ./hmy --node=[NODE] balances 34 | 35 | %s 36 | ./hmy --node=[NODE] blockchain transaction-by-hash 37 | 38 | %s 39 | ./hmy keys list 40 | 41 | %s 42 | ./hmy --node=[NODE] transfer \ 43 | --from --to \ 44 | --from-shard 0 --to-shard 1 --amount 200 --passphrase 45 | 46 | %s 47 | ./hmy --node=[NODE] transfer --file 48 | Check README for details on json file format. 49 | 50 | %s 51 | ./hmy --node=[NODE] blockchain transaction-receipt 52 | 53 | %s 54 | ./hmy keys recover-from-mnemonic --passphrase 55 | 56 | %s 57 | ./hmy keys import-ks 58 | 59 | %s 60 | ./hmy keys import-private-key 61 | 62 | %s 63 | ./hmy keys export-private-key --passphrase 64 | 65 | %s 66 | ./hmy keys generate-bls-key --bls-file-path 67 | 68 | %s 69 | ./hmy --node=[NODE] staking create-validator --amount 10 --validator-addr \ 70 | --bls-pubkeys ,, \ 71 | --identity foo --details bar --name baz --max-change-rate 0.1 --max-rate 0.1 --max-total-delegation 10 \ 72 | --min-self-delegation 10 --rate 0.1 --security-contact Leo --website harmony.one --passphrase 73 | 74 | %s 75 | ./hmy --node=[NODE] staking edit-validator \ 76 | --validator-addr --identity foo --details bar \ 77 | --name baz --security-contact EK --website harmony.one \ 78 | --min-self-delegation 0 --max-total-delegation 10 --rate 0.1\ 79 | --add-bls-key --remove-bls-key --passphrase 80 | 81 | %s 82 | ./hmy --node=[NODE] staking delegate \ 83 | --delegator-addr --validator-addr \ 84 | --amount 10 --passphrase 85 | 86 | %s 87 | ./hmy --node=[NODE] staking undelegate \ 88 | --delegator-addr --validator-addr \ 89 | --amount 10 --passphrase 90 | 91 | %s 92 | ./hmy --node=[NODE] staking collect-rewards \ 93 | --delegator-addr --passphrase 94 | 95 | %s 96 | ./hmy --node=[NODE] blockchain validator elected 97 | 98 | %s 99 | ./hmy --node=[NODE] blockchain utility-metrics 100 | 101 | %s 102 | ./hmy --node=[NODE] failures staking 103 | 104 | %s 105 | ./hmy --node=[NODE] utility shard-for-bls 106 | 107 | %s 108 | ./hmy governance vote-proposal --space=[harmony-mainnet.eth] \ 109 | --proposal= --proposal-type=[single-choice] \ 110 | --choice= --app=[APP] --key= 111 | PS: key must first use (hmy keys import-private-key) to import 112 | 113 | %s 114 | ./hmy command --net=testnet 115 | `, 116 | g("1. Check account balance on given chain"), 117 | g("2. Check sent transaction"), 118 | g("3. List local account keys"), 119 | g("4. Sending a transaction (waits 40 seconds for transaction confirmation)"), 120 | g("5. Sending a batch of transactions as dictated from a file (the `--dry-run` options still apply)"), 121 | g("6. Check a completed transaction receipt"), 122 | g("7. Import an account using the mnemonic. Prompts the user to give the mnemonic."), 123 | g("8. Import an existing keystore file"), 124 | g("9. Import a keystore file using a secp256k1 private key"), 125 | g("10. Export a keystore file's secp256k1 private key"), 126 | g("11. Generate a BLS key then encrypt and save the private key to the specified location."), 127 | g("12. Create a new validator with a list of BLS keys"), 128 | g("13. Edit an existing validator"), 129 | g("14. Delegate an amount to a validator"), 130 | g("15. Undelegate to a validator"), 131 | g("16. Collect block rewards as a delegator"), 132 | g("17. Check elected validators"), 133 | g("18. Get current staking utility metrics"), 134 | g("19. Check in-memory record of failed staking transactions"), 135 | g("20. Check which shard your BLS public key would be assigned to as a validator"), 136 | g("21. Vote on a governance proposal on https://snapshot.org"), 137 | g("22. Enter console"), 138 | ) 139 | ) 140 | -------------------------------------------------------------------------------- /pkg/rpc/eth/methods.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | rpcCommon "github.com/harmony-one/go-sdk/pkg/rpc/common" 7 | ) 8 | 9 | const ( 10 | prefix = "eth" 11 | ) 12 | 13 | // Method is a list of known RPC methods 14 | var Method = rpcCommon.RpcEnumList{ 15 | GetShardingStructure: fmt.Sprintf("%s_getShardingStructure", prefix), 16 | GetNodeMetadata: fmt.Sprintf("%s_getNodeMetadata", prefix), 17 | GetLatestBlockHeader: fmt.Sprintf("%s_latestHeader", prefix), 18 | GetBlockByHash: fmt.Sprintf("%s_getBlockByHash", prefix), 19 | GetBlockByNumber: fmt.Sprintf("%s_getBlockByNumber", prefix), 20 | GetBlockTransactionCountByHash: fmt.Sprintf("%s_getBlockTransactionCountByHash", prefix), 21 | GetBlockTransactionCountByNumber: fmt.Sprintf("%s_getBlockTransactionCountByNumber", prefix), 22 | GetCode: fmt.Sprintf("%s_getCode", prefix), 23 | GetTransactionByBlockHashAndIndex: fmt.Sprintf("%s_getTransactionByBlockHashAndIndex", prefix), 24 | GetTransactionByBlockNumberAndIndex: fmt.Sprintf("%s_getTransactionByBlockNumberAndIndex", prefix), 25 | GetTransactionByHash: fmt.Sprintf("%s_getTransactionByHash", prefix), 26 | GetStakingTransactionByHash: fmt.Sprintf("%s_getStakingTransactionByHash", prefix), 27 | GetTransactionReceipt: fmt.Sprintf("%s_getTransactionReceipt", prefix), 28 | Syncing: fmt.Sprintf("%s_syncing", prefix), 29 | PeerCount: "net_peerCount", 30 | GetBalance: fmt.Sprintf("%s_getBalance", prefix), 31 | GetStorageAt: fmt.Sprintf("%s_getStorageAt", prefix), 32 | GetTransactionCount: fmt.Sprintf("%s_getTransactionCount", prefix), 33 | SendTransaction: fmt.Sprintf("%s_sendTransaction", prefix), 34 | SendRawTransaction: fmt.Sprintf("%s_sendRawTransaction", prefix), 35 | Subscribe: fmt.Sprintf("%s_subscribe", prefix), 36 | GetPastLogs: fmt.Sprintf("%s_getLogs", prefix), 37 | GetWork: fmt.Sprintf("%s_getWork", prefix), 38 | GetProof: fmt.Sprintf("%s_getProof", prefix), 39 | GetFilterChanges: fmt.Sprintf("%s_getFilterChanges", prefix), 40 | NewPendingTransactionFilter: fmt.Sprintf("%s_newPendingTransactionFilter", prefix), 41 | NewBlockFilter: fmt.Sprintf("%s_newBlockFilter", prefix), 42 | NewFilter: fmt.Sprintf("%s_newFilter", prefix), 43 | Call: fmt.Sprintf("%s_call", prefix), 44 | EstimateGas: fmt.Sprintf("%s_estimateGas", prefix), 45 | GasPrice: fmt.Sprintf("%s_gasPrice", prefix), 46 | BlockNumber: fmt.Sprintf("%s_blockNumber", prefix), 47 | UnSubscribe: fmt.Sprintf("%s_unsubscribe", prefix), 48 | NetVersion: "net_version", 49 | ProtocolVersion: fmt.Sprintf("%s_protocolVersion", prefix), 50 | SendRawStakingTransaction: fmt.Sprintf("%s_sendRawStakingTransaction", prefix), 51 | GetElectedValidatorAddresses: fmt.Sprintf("%s_getElectedValidatorAddresses", prefix), 52 | GetAllValidatorAddresses: fmt.Sprintf("%s_getAllValidatorAddresses", prefix), 53 | GetValidatorInformation: fmt.Sprintf("%s_getValidatorInformation", prefix), 54 | GetAllValidatorInformation: fmt.Sprintf("%s_getAllValidatorInformation", prefix), 55 | GetValidatorInformationByBlockNumber: fmt.Sprintf("%s_getValidatorInformationByBlockNumber", prefix), 56 | GetAllValidatorInformationByBlockNumber: fmt.Sprintf("%s_getAllValidatorInformationByBlockNumber", prefix), 57 | GetDelegationsByDelegator: fmt.Sprintf("%s_getDelegationsByDelegator", prefix), 58 | GetDelegationsByValidator: fmt.Sprintf("%s_getDelegationsByValidator", prefix), 59 | GetCurrentTransactionErrorSink: fmt.Sprintf("%s_getCurrentTransactionErrorSink", prefix), 60 | GetMedianRawStakeSnapshot: fmt.Sprintf("%s_getMedianRawStakeSnapshot", prefix), 61 | GetCurrentStakingErrorSink: fmt.Sprintf("%s_getCurrentStakingErrorSink", prefix), 62 | GetTransactionsHistory: fmt.Sprintf("%s_getTransactionsHistory", prefix), 63 | GetPendingTxnsInPool: fmt.Sprintf("%s_pendingTransactions", prefix), 64 | GetPendingCrosslinks: fmt.Sprintf("%s_getPendingCrossLinks", prefix), 65 | GetPendingCXReceipts: fmt.Sprintf("%s_getPendingCXReceipts", prefix), 66 | GetCurrentUtilityMetrics: fmt.Sprintf("%s_getCurrentUtilityMetrics", prefix), 67 | ResendCX: fmt.Sprintf("%s_resendCx", prefix), 68 | GetSuperCommmittees: fmt.Sprintf("%s_getSuperCommittees", prefix), 69 | GetCurrentBadBlocks: fmt.Sprintf("%s_getCurrentBadBlocks", prefix), 70 | GetShardID: fmt.Sprintf("%s_getShardID", prefix), 71 | GetLastCrossLinks: fmt.Sprintf("%s_getLastCrossLinks", prefix), 72 | GetLatestChainHeaders: fmt.Sprintf("%s_getLatestChainHeaders", prefix), 73 | } 74 | -------------------------------------------------------------------------------- /pkg/rpc/v1/methods.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "fmt" 5 | 6 | rpcCommon "github.com/harmony-one/go-sdk/pkg/rpc/common" 7 | ) 8 | 9 | const ( 10 | prefix = "hmy" 11 | ) 12 | 13 | // Method is a list of known RPC methods 14 | var Method = rpcCommon.RpcEnumList{ 15 | GetShardingStructure: fmt.Sprintf("%s_getShardingStructure", prefix), 16 | GetNodeMetadata: fmt.Sprintf("%s_getNodeMetadata", prefix), 17 | GetLatestBlockHeader: fmt.Sprintf("%s_latestHeader", prefix), 18 | GetBlockByHash: fmt.Sprintf("%s_getBlockByHash", prefix), 19 | GetBlockByNumber: fmt.Sprintf("%s_getBlockByNumber", prefix), 20 | GetBlockTransactionCountByHash: fmt.Sprintf("%s_getBlockTransactionCountByHash", prefix), 21 | GetBlockTransactionCountByNumber: fmt.Sprintf("%s_getBlockTransactionCountByNumber", prefix), 22 | GetCode: fmt.Sprintf("%s_getCode", prefix), 23 | GetTransactionByBlockHashAndIndex: fmt.Sprintf("%s_getTransactionByBlockHashAndIndex", prefix), 24 | GetTransactionByBlockNumberAndIndex: fmt.Sprintf("%s_getTransactionByBlockNumberAndIndex", prefix), 25 | GetTransactionByHash: fmt.Sprintf("%s_getTransactionByHash", prefix), 26 | GetStakingTransactionByHash: fmt.Sprintf("%s_getStakingTransactionByHash", prefix), 27 | GetTransactionReceipt: fmt.Sprintf("%s_getTransactionReceipt", prefix), 28 | Syncing: fmt.Sprintf("%s_syncing", prefix), 29 | PeerCount: "net_peerCount", 30 | GetBalance: fmt.Sprintf("%s_getBalance", prefix), 31 | GetStorageAt: fmt.Sprintf("%s_getStorageAt", prefix), 32 | GetTransactionCount: fmt.Sprintf("%s_getTransactionCount", prefix), 33 | SendTransaction: fmt.Sprintf("%s_sendTransaction", prefix), 34 | SendRawTransaction: fmt.Sprintf("%s_sendRawTransaction", prefix), 35 | Subscribe: fmt.Sprintf("%s_subscribe", prefix), 36 | GetPastLogs: fmt.Sprintf("%s_getLogs", prefix), 37 | GetWork: fmt.Sprintf("%s_getWork", prefix), 38 | GetProof: fmt.Sprintf("%s_getProof", prefix), 39 | GetFilterChanges: fmt.Sprintf("%s_getFilterChanges", prefix), 40 | NewPendingTransactionFilter: fmt.Sprintf("%s_newPendingTransactionFilter", prefix), 41 | NewBlockFilter: fmt.Sprintf("%s_newBlockFilter", prefix), 42 | NewFilter: fmt.Sprintf("%s_newFilter", prefix), 43 | Call: fmt.Sprintf("%s_call", prefix), 44 | EstimateGas: fmt.Sprintf("%s_estimateGas", prefix), 45 | GasPrice: fmt.Sprintf("%s_gasPrice", prefix), 46 | BlockNumber: fmt.Sprintf("%s_blockNumber", prefix), 47 | UnSubscribe: fmt.Sprintf("%s_unsubscribe", prefix), 48 | NetVersion: "net_version", 49 | ProtocolVersion: fmt.Sprintf("%s_protocolVersion", prefix), 50 | SendRawStakingTransaction: fmt.Sprintf("%s_sendRawStakingTransaction", prefix), 51 | GetElectedValidatorAddresses: fmt.Sprintf("%s_getElectedValidatorAddresses", prefix), 52 | GetAllValidatorAddresses: fmt.Sprintf("%s_getAllValidatorAddresses", prefix), 53 | GetValidatorInformation: fmt.Sprintf("%s_getValidatorInformation", prefix), 54 | GetAllValidatorInformation: fmt.Sprintf("%s_getAllValidatorInformation", prefix), 55 | GetValidatorInformationByBlockNumber: fmt.Sprintf("%s_getValidatorInformationByBlockNumber", prefix), 56 | GetAllValidatorInformationByBlockNumber: fmt.Sprintf("%s_getAllValidatorInformationByBlockNumber", prefix), 57 | GetDelegationsByDelegator: fmt.Sprintf("%s_getDelegationsByDelegator", prefix), 58 | GetDelegationsByValidator: fmt.Sprintf("%s_getDelegationsByValidator", prefix), 59 | GetCurrentTransactionErrorSink: fmt.Sprintf("%s_getCurrentTransactionErrorSink", prefix), 60 | GetMedianRawStakeSnapshot: fmt.Sprintf("%s_getMedianRawStakeSnapshot", prefix), 61 | GetCurrentStakingErrorSink: fmt.Sprintf("%s_getCurrentStakingErrorSink", prefix), 62 | GetTransactionsHistory: fmt.Sprintf("%s_getTransactionsHistory", prefix), 63 | GetPendingTxnsInPool: fmt.Sprintf("%s_pendingTransactions", prefix), 64 | GetPendingCrosslinks: fmt.Sprintf("%s_getPendingCrossLinks", prefix), 65 | GetPendingCXReceipts: fmt.Sprintf("%s_getPendingCXReceipts", prefix), 66 | GetCurrentUtilityMetrics: fmt.Sprintf("%s_getCurrentUtilityMetrics", prefix), 67 | ResendCX: fmt.Sprintf("%s_resendCx", prefix), 68 | GetSuperCommmittees: fmt.Sprintf("%s_getSuperCommittees", prefix), 69 | GetCurrentBadBlocks: fmt.Sprintf("%s_getCurrentBadBlocks", prefix), 70 | GetShardID: fmt.Sprintf("%s_getShardID", prefix), 71 | GetLastCrossLinks: fmt.Sprintf("%s_getLastCrossLinks", prefix), 72 | GetLatestChainHeaders: fmt.Sprintf("%s_getLatestChainHeaders", prefix), 73 | } 74 | -------------------------------------------------------------------------------- /pkg/ledger/hw_wallet.go: -------------------------------------------------------------------------------- 1 | package ledger 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "math/big" 8 | "os" 9 | "sync" 10 | 11 | "github.com/pkg/errors" 12 | "golang.org/x/crypto/sha3" 13 | 14 | "github.com/ethereum/go-ethereum/crypto" 15 | "github.com/ethereum/go-ethereum/rlp" 16 | "github.com/harmony-one/go-sdk/pkg/address" 17 | "github.com/harmony-one/harmony/core/types" 18 | staking "github.com/harmony-one/harmony/staking/types" 19 | ) 20 | 21 | var ( 22 | nanos *NanoS //singleton 23 | once sync.Once 24 | ) 25 | 26 | func getLedger() *NanoS { 27 | once.Do(func() { 28 | var err error 29 | nanos, err = OpenNanoS() 30 | if err != nil { 31 | log.Fatalln("Couldn't open device:", err) 32 | os.Exit(-1) 33 | } 34 | }) 35 | 36 | return nanos 37 | } 38 | 39 | //ProcessAddressCommand list the address associated with Ledger Nano S 40 | func GetAddress() string { 41 | n := getLedger() 42 | oneAddr, err := n.GetAddress() 43 | if err != nil { 44 | log.Fatalln("Couldn't get one address:", err) 45 | os.Exit(-1) 46 | } 47 | 48 | return oneAddr 49 | } 50 | 51 | //ProcessAddressCommand list the address associated with Ledger Nano S 52 | func ProcessAddressCommand() { 53 | n := getLedger() 54 | oneAddr, err := n.GetAddress() 55 | if err != nil { 56 | log.Fatalln("Couldn't get one address:", err) 57 | os.Exit(-1) 58 | } 59 | 60 | fmt.Printf("%-24s\t\t%23s\n", "NAME", "ADDRESS") 61 | fmt.Printf("%-48s\t%s\n", "Ledger Nano S", oneAddr) 62 | } 63 | 64 | // SignTx signs the given transaction with the requested account. 65 | func SignTx(tx *types.Transaction, chainID *big.Int) ([]byte, string, error) { 66 | var rlpEncodedTx []byte 67 | 68 | // Depending on the presence of the chain ID, sign with EIP155 or frontier 69 | if chainID != nil { 70 | rlpEncodedTx, _ = rlp.EncodeToBytes( 71 | []interface{}{ 72 | tx.Nonce(), 73 | tx.GasPrice(), 74 | tx.GasLimit(), 75 | tx.ShardID(), 76 | tx.ToShardID(), 77 | tx.To(), 78 | tx.Value(), 79 | tx.Data(), 80 | chainID, uint(0), uint(0), 81 | }) 82 | } else { 83 | rlpEncodedTx, _ = rlp.EncodeToBytes( 84 | []interface{}{ 85 | tx.Nonce(), 86 | tx.GasPrice(), 87 | tx.GasLimit(), 88 | tx.ShardID(), 89 | tx.ToShardID(), 90 | tx.To(), 91 | tx.Value(), 92 | tx.Data(), 93 | }) 94 | } 95 | 96 | n := getLedger() 97 | sig, err := n.SignTxn(rlpEncodedTx) 98 | if err != nil { 99 | log.Println("Couldn't sign transaction, error:", err) 100 | return nil, "", err 101 | } 102 | 103 | var hashBytes [32]byte 104 | hw := sha3.NewLegacyKeccak256() 105 | hw.Write(rlpEncodedTx[:]) 106 | hw.Sum(hashBytes[:0]) 107 | 108 | pubkey, err := crypto.Ecrecover(hashBytes[:], sig[:]) 109 | if err != nil { 110 | log.Println("Ecrecover failed :", err) 111 | return nil, "", err 112 | } 113 | 114 | if len(pubkey) == 0 || pubkey[0] != 4 { 115 | log.Println("invalid public key") 116 | return nil, "", err 117 | } 118 | 119 | pubBytes := crypto.Keccak256(pubkey[1:65])[12:] 120 | signerAddr, _ := address.ConvertAndEncode("one", pubBytes) 121 | 122 | var r, s, v *big.Int 123 | if chainID != nil { 124 | r, s, v, err = eip155SignerSignatureValues(chainID, sig[:]) 125 | } else { 126 | r, s, v, err = frontierSignatureValues(sig[:]) 127 | } 128 | 129 | if err != nil { 130 | log.Println(err) 131 | return nil, "", err 132 | } 133 | 134 | // Depending on the presence of the chain ID, sign with EIP155 or frontier 135 | rawTx, err := rlp.EncodeToBytes( 136 | []interface{}{ 137 | tx.Nonce(), 138 | tx.GasPrice(), 139 | tx.GasLimit(), 140 | tx.ShardID(), 141 | tx.ToShardID(), 142 | tx.To(), 143 | tx.Value(), 144 | tx.Data(), 145 | v, 146 | r, 147 | s, 148 | }) 149 | 150 | return rawTx, signerAddr, err 151 | } 152 | 153 | func frontierSignatureValues(sig []byte) (r, s, v *big.Int, err error) { 154 | if len(sig) != 65 { 155 | return nil, nil, nil, errors.New("get signature with wrong size from ledger nano") 156 | } 157 | r = new(big.Int).SetBytes(sig[:32]) 158 | s = new(big.Int).SetBytes(sig[32:64]) 159 | v = new(big.Int).SetBytes([]byte{sig[64] + 27}) 160 | return r, s, v, nil 161 | } 162 | 163 | func eip155SignerSignatureValues(chainID *big.Int, sig []byte) (R, S, V *big.Int, err error) { 164 | R, S, V, err = frontierSignatureValues(sig) 165 | if err != nil { 166 | return nil, nil, nil, err 167 | } 168 | 169 | chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2)) 170 | if chainID.Sign() != 0 { 171 | V = big.NewInt(int64(sig[64] + 35)) 172 | V.Add(V, chainIDMul) 173 | } 174 | return R, S, V, nil 175 | } 176 | 177 | // SignTx signs the given transaction with ledger. 178 | func SignStakingTx(tx *staking.StakingTransaction, chainID *big.Int) (*staking.StakingTransaction, string, error) { 179 | //get the RLP encoding of raw staking with R,S,V = 0 180 | w := &bytes.Buffer{} 181 | err := tx.EncodeRLP(w) 182 | if err != nil { 183 | return nil, "", err 184 | } 185 | rlpEncodedTx := w.Bytes() 186 | 187 | //get the RLP encoding of chain data 188 | chainData, _ := rlp.EncodeToBytes([]interface{}{ 189 | chainID, uint(0), uint(0), 190 | }) 191 | 192 | //replace R,S,V with RLP encoded (chainID, 0, 0) 193 | rlpEncodedTx = append(rlpEncodedTx[0:len(rlpEncodedTx)-3], chainData[1:]...) 194 | 195 | //send the RLP encoded staking tx to ledger 196 | n := getLedger() 197 | sig, err := n.SignStaking(rlpEncodedTx) 198 | if err != nil { 199 | log.Println("Couldn't sign staking transaction, error:", err) 200 | return nil, "", err 201 | } 202 | 203 | var hashBytes [32]byte 204 | hw := sha3.NewLegacyKeccak256() 205 | hw.Write(rlpEncodedTx[:]) 206 | hw.Sum(hashBytes[:0]) 207 | 208 | pubkey, err := crypto.Ecrecover(hashBytes[:], sig[:]) 209 | if err != nil { 210 | log.Println("Ecrecover failed :", err) 211 | return nil, "", err 212 | } 213 | 214 | if len(pubkey) == 0 || pubkey[0] != 4 { 215 | log.Println("invalid public key") 216 | return nil, "", err 217 | } 218 | 219 | pubBytes := crypto.Keccak256(pubkey[1:65])[12:] 220 | signerAddr, _ := address.ConvertAndEncode("one", pubBytes) 221 | 222 | // WithSignature returns a new transaction with the given signature. 223 | rawTx, err := tx.WithSignature(staking.NewEIP155Signer(chainID), sig[:]) 224 | return rawTx, signerAddr, err 225 | } 226 | -------------------------------------------------------------------------------- /pkg/governance/eip712.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "math/big" 8 | "strings" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/ethereum/go-ethereum/common/math" 12 | "github.com/ethereum/go-ethereum/crypto" 13 | "github.com/ethereum/go-ethereum/signer/core" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | // This embedded type was created to override the EncodeData function 18 | // and remove the validation for a mandatory chain id 19 | type TypedData struct { 20 | core.TypedData 21 | } 22 | 23 | // dataMismatchError generates an error for a mismatch between 24 | // the provided type and data 25 | func dataMismatchError(encType string, encValue interface{}) error { 26 | return fmt.Errorf("provided data '%v' doesn't match type '%s'", encValue, encType) 27 | } 28 | 29 | // EncodeData generates the following encoding: 30 | // `enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ)` 31 | // 32 | // each encoded member is 32-byte long 33 | // This method overridden here to remove the validation for mandatory chain id 34 | func (typedData *TypedData) EncodeData(primaryType string, data map[string]interface{}, depth int) (hexutil.Bytes, error) { 35 | // if err := typedData.validate(); err != nil { 36 | // return nil, err 37 | // } 38 | 39 | buffer := bytes.Buffer{} 40 | 41 | // Verify extra data 42 | if len(typedData.Types[primaryType]) < len(data) { 43 | return nil, errors.New("there is extra data provided in the message") 44 | } 45 | 46 | // Add typehash 47 | buffer.Write(typedData.TypeHash(primaryType)) 48 | 49 | // Add field contents. Structs and arrays have special handlers. 50 | for _, field := range typedData.Types[primaryType] { 51 | encType := field.Type 52 | encValue := data[field.Name] 53 | if encType[len(encType)-1:] == "]" { 54 | arrayValue, ok := encValue.([]interface{}) 55 | if !ok { 56 | return nil, dataMismatchError(encType, encValue) 57 | } 58 | 59 | arrayBuffer := bytes.Buffer{} 60 | parsedType := strings.Split(encType, "[")[0] 61 | for _, item := range arrayValue { 62 | if typedData.Types[parsedType] != nil { 63 | mapValue, ok := item.(map[string]interface{}) 64 | if !ok { 65 | return nil, dataMismatchError(parsedType, item) 66 | } 67 | encodedData, err := typedData.EncodeData(parsedType, mapValue, depth+1) 68 | if err != nil { 69 | return nil, err 70 | } 71 | arrayBuffer.Write(encodedData) 72 | } else { 73 | bytesValue, err := typedData.EncodePrimitiveValue(parsedType, item, depth) 74 | if err != nil { 75 | return nil, err 76 | } 77 | arrayBuffer.Write(bytesValue) 78 | } 79 | } 80 | 81 | buffer.Write(crypto.Keccak256(arrayBuffer.Bytes())) 82 | } else if typedData.Types[field.Type] != nil { 83 | mapValue, ok := encValue.(map[string]interface{}) 84 | if !ok { 85 | return nil, dataMismatchError(encType, encValue) 86 | } 87 | encodedData, err := typedData.EncodeData(field.Type, mapValue, depth+1) 88 | if err != nil { 89 | return nil, err 90 | } 91 | buffer.Write(crypto.Keccak256(encodedData)) 92 | } else { 93 | byteValue, err := typedData.EncodePrimitiveValue(encType, encValue, depth) 94 | if err != nil { 95 | return nil, err 96 | } 97 | buffer.Write(byteValue) 98 | } 99 | } 100 | return buffer.Bytes(), nil 101 | } 102 | 103 | type TypedDataMessage = map[string]interface{} 104 | 105 | // HashStruct generates a keccak256 hash of the encoding of the provided data 106 | // This method overridden here to allow calling the overriden EncodeData above 107 | func (typedData *TypedData) HashStruct(primaryType string, data TypedDataMessage) (hexutil.Bytes, error) { 108 | encodedData, err := typedData.EncodeData(primaryType, data, 1) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return crypto.Keccak256(encodedData), nil 113 | } 114 | 115 | func (typedData *TypedData) String() (string, error) { 116 | type domain struct { 117 | Name string `json:"name"` 118 | Version string `json:"version"` 119 | } 120 | // this data structure created to remove unused fields 121 | // for example, domain type is not sent in post request 122 | // and neither are the blank fields in the domain 123 | type data struct { 124 | Domain domain `json:"domain"` 125 | Types core.Types `json:"types"` 126 | Message core.TypedDataMessage `json:"message"` 127 | } 128 | var ts uint64 129 | var err error 130 | if ts, err = toUint64(typedData.Message["timestamp"]); err != nil { 131 | // should not happen 132 | return "", errors.Wrapf(err, "timestamp") 133 | } 134 | formatted := data{ 135 | Domain: domain{ 136 | Name: typedData.Domain.Name, 137 | Version: typedData.Domain.Version, 138 | }, 139 | Types: core.Types{ 140 | typedData.PrimaryType: typedData.Types[typedData.PrimaryType], 141 | }, 142 | Message: core.TypedDataMessage{ 143 | "space": typedData.Message["space"], 144 | "proposal": typedData.Message["proposal"], 145 | "choice": typedData.Message["choice"], 146 | "app": typedData.Message["app"], 147 | "reason": typedData.Message["reason"], 148 | // this conversion is required to stop snapshot 149 | // from complaining about `wrong envelope format` 150 | "timestamp": ts, 151 | "from": typedData.Message["from"], 152 | }, 153 | } 154 | // same comment as above 155 | if typedData.Types["Vote"][4].Type == "uint32" { 156 | if choice, err := toUint64(typedData.Message["choice"]); err != nil { 157 | return "", errors.Wrapf(err, "choice") 158 | } else { 159 | formatted.Message["choice"] = choice 160 | } 161 | // prevent hex choice interpretation 162 | } else if typedData.Types["Vote"][4].Type == "uint32[]" { 163 | arr := typedData.Message["choice"].([]interface{}) 164 | res := make([]uint64, len(arr)) 165 | for i, a := range arr { 166 | if c, err := toUint64(a); err != nil { 167 | return "", errors.Wrapf(err, "choice member %d", i) 168 | } else { 169 | res[i] = c 170 | } 171 | } 172 | formatted.Message["choice"] = res 173 | } 174 | message, err := json.Marshal(formatted) 175 | if err != nil { 176 | return "", err 177 | } else { 178 | return string(message), nil 179 | } 180 | } 181 | 182 | func toUint64(x interface{}) (uint64, error) { 183 | y, ok := x.(*math.HexOrDecimal256) 184 | if !ok { 185 | return 0, errors.New( 186 | fmt.Sprintf("%+v is not a *math.HexOrDecimal256", x), 187 | ) 188 | } 189 | z := (*big.Int)(y) 190 | return z.Uint64(), nil 191 | } -------------------------------------------------------------------------------- /cmd/subcommands/blockchain.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/harmony-one/go-sdk/pkg/common" 7 | "github.com/harmony-one/go-sdk/pkg/rpc" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | addr oneAddress 13 | size int64 14 | ) 15 | 16 | func init() { 17 | cmdValidator := &cobra.Command{ 18 | Use: "validator", 19 | Short: "information about validators", 20 | Long: ` 21 | Look up information about validator information 22 | `, 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | cmd.Help() 25 | return nil 26 | }, 27 | } 28 | 29 | cmdDelegation := &cobra.Command{ 30 | Use: "delegation", 31 | Short: "information about delegations", 32 | Long: ` 33 | Look up information about delegation 34 | `, 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | cmd.Help() 37 | return nil 38 | }, 39 | } 40 | 41 | cmdBlockchain := &cobra.Command{ 42 | Use: "blockchain", 43 | Short: "Interact with the Harmony.one Blockchain", 44 | Long: ` 45 | Query Harmony's blockchain for completed transaction, historic records 46 | `, 47 | RunE: func(cmd *cobra.Command, args []string) error { 48 | cmd.Help() 49 | return nil 50 | }, 51 | } 52 | 53 | accountHistorySubCmd := &cobra.Command{ 54 | Use: "account-history", 55 | Short: "Get history of all transactions for given account", 56 | Long: ` 57 | High level information about each transaction for given account 58 | `, 59 | Args: cobra.ExactArgs(1), 60 | PreRunE: validateAddress, 61 | RunE: func(cmd *cobra.Command, args []string) error { 62 | type historyParams struct { 63 | Address string `json:"address"` 64 | PageIndex int64 `json:"pageIndex"` 65 | PageSize int64 `json:"pageSize"` 66 | FullTx bool `json:"fullTx"` 67 | TxType string `json:"txType"` 68 | Order string `json:"order"` 69 | } 70 | noLatest = true 71 | params := historyParams{args[0], 0, size, true, "", ""} 72 | return request(rpc.Method.GetTransactionsHistory, []interface{}{params}) 73 | }, 74 | } 75 | 76 | accountHistorySubCmd.Flags().Int64Var(&size, "max-tx", 1000, "max number of transactions to list") 77 | 78 | subCommands := []*cobra.Command{{ 79 | Use: "block-by-number", 80 | Short: "Get a harmony blockchain block by block number", 81 | Args: cobra.ExactArgs(1), 82 | RunE: func(cmd *cobra.Command, args []string) error { 83 | noLatest = true 84 | return request(rpc.Method.GetBlockByNumber, []interface{}{args[0], true}) 85 | }, 86 | }, { 87 | Use: "known-chains", 88 | Short: "Print out the known chain-ids", 89 | RunE: func(cmd *cobra.Command, args []string) error { 90 | fmt.Println(common.Chain.String()) 91 | return nil 92 | }, 93 | }, { 94 | Use: "protocol-version", 95 | Short: "The version of the Harmony Protocol", 96 | Long: ` 97 | Query Harmony's blockchain for high level metrics, queries 98 | `, 99 | RunE: func(cmd *cobra.Command, args []string) error { 100 | return request(rpc.Method.ProtocolVersion, []interface{}{}) 101 | }, 102 | }, { 103 | Use: "transaction-by-hash", 104 | Short: "Get transaction by hash", 105 | Args: cobra.ExactArgs(1), 106 | Long: ` 107 | Inputs of a transaction and r, s, v value of transaction 108 | `, 109 | RunE: func(cmd *cobra.Command, args []string) error { 110 | noLatest = true 111 | return request(rpc.Method.GetTransactionByHash, []interface{}{args[0]}) 112 | }, 113 | }, { 114 | Use: "staking-transaction-by-hash", 115 | Short: "Get staking transaction by hash", 116 | Args: cobra.ExactArgs(1), 117 | Long: ` 118 | Inputs of a transaction and r, s, v value of transaction 119 | `, 120 | RunE: func(cmd *cobra.Command, args []string) error { 121 | noLatest = true 122 | return request(rpc.Method.GetStakingTransactionByHash, []interface{}{args[0]}) 123 | }, 124 | }, { 125 | Use: "transaction-receipt", 126 | Short: "Get information about a finalized transaction", 127 | Args: cobra.ExactArgs(1), 128 | Long: ` 129 | High level information about transaction, like blockNumber, blockHash 130 | `, 131 | RunE: func(cmd *cobra.Command, args []string) error { 132 | noLatest = true 133 | return request(rpc.Method.GetTransactionReceipt, []interface{}{args[0]}) 134 | }, 135 | }, { 136 | Use: "median-stake", 137 | Short: "median stake of top 320 validators with delegations applied stake (pre-epos processing)", 138 | RunE: func(cmd *cobra.Command, args []string) error { 139 | noLatest = true 140 | return request(rpc.Method.GetMedianRawStakeSnapshot, []interface{}{}) 141 | }, 142 | }, { 143 | Use: "current-nonce", 144 | Short: "Current nonce of an account", 145 | Args: cobra.ExactArgs(1), 146 | Long: `Current nonce number of a one-address`, 147 | PreRunE: validateAddress, 148 | RunE: func(cmd *cobra.Command, args []string) error { 149 | return request(rpc.Method.GetTransactionCount, []interface{}{addr.address}) 150 | }, 151 | }, { 152 | Use: "pool", 153 | Short: "Dump a node's transaction pool", 154 | RunE: func(cmd *cobra.Command, args []string) error { 155 | noLatest = true 156 | return request(rpc.Method.GetPendingTxnsInPool, []interface{}{}) 157 | }, 158 | }, { 159 | Use: "latest-header", 160 | Short: "Get the latest header", 161 | RunE: func(cmd *cobra.Command, args []string) error { 162 | noLatest = true 163 | return request(rpc.Method.GetLatestBlockHeader, []interface{}{}) 164 | }, 165 | }, { 166 | Use: "latest-headers", 167 | Short: "Get the latest chain headers", 168 | RunE: func(cmd *cobra.Command, args []string) error { 169 | noLatest = true 170 | return request(rpc.Method.GetLatestChainHeaders, []interface{}{}) 171 | }, 172 | }, { 173 | Use: "resend-cx", 174 | Short: "Re-play a cross shard transaction", 175 | Args: cobra.ExactArgs(1), 176 | RunE: func(cmd *cobra.Command, args []string) error { 177 | noLatest = true 178 | return request(rpc.Method.ResendCX, []interface{}{args[0]}) 179 | }, 180 | }, { 181 | Use: "utility-metrics", 182 | Short: "Current utility metrics", 183 | Long: `Current staking utility metrics`, 184 | RunE: func(cmd *cobra.Command, args []string) error { 185 | noLatest = true 186 | return request(rpc.Method.GetCurrentUtilityMetrics, []interface{}{}) 187 | }, 188 | }, 189 | accountHistorySubCmd, 190 | } 191 | 192 | cmdBlockchain.AddCommand(cmdValidator) 193 | cmdBlockchain.AddCommand(cmdDelegation) 194 | cmdValidator.AddCommand(validatorSubCmds[:]...) 195 | cmdDelegation.AddCommand(delegationSubCmds[:]...) 196 | cmdBlockchain.AddCommand(subCommands[:]...) 197 | RootCmd.AddCommand(cmdBlockchain) 198 | } 199 | -------------------------------------------------------------------------------- /pkg/governance/types.go: -------------------------------------------------------------------------------- 1 | package governance 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/ethereum/go-ethereum/common/math" 12 | eip712 "github.com/ethereum/go-ethereum/signer/core" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | var ( 17 | voteToNumberMapping = map[string]int64{ 18 | "for": 1, 19 | "against": 2, 20 | "abstain": 3, 21 | } 22 | ) 23 | 24 | type Vote struct { 25 | From string // --key 26 | Space string // --space 27 | Proposal string // --proposal 28 | ProposalType string // --proposal-type 29 | Choice string // --choice 30 | // Privacy string // --privacy 31 | App string // --app 32 | Reason string // --reason 33 | Timestamp int64 // not exposed to the end user 34 | } 35 | 36 | func (v *Vote) ToEIP712() (*TypedData, error) { 37 | // common types regardless of parameters 38 | // key `app` appended later because order matters 39 | myType := []eip712.Type{ 40 | { 41 | Name: "from", 42 | Type: "address", 43 | }, 44 | { 45 | Name: "space", 46 | Type: "string", 47 | }, 48 | { 49 | Name: "timestamp", 50 | Type: "uint64", 51 | }, 52 | } 53 | 54 | var proposal interface{} 55 | isHex := strings.HasPrefix(v.Proposal, "0x") 56 | if isHex { 57 | myType = append(myType, eip712.Type{ 58 | Name: "proposal", 59 | Type: "bytes32", 60 | }) 61 | if proposalBytes, err := hexutil.Decode(v.Proposal); err != nil { 62 | return nil, errors.Wrapf( 63 | err, "invalid proposal hash %s", v.Proposal, 64 | ) 65 | } else { 66 | // EncodePrimitiveValue accepts only hexutil.Bytes not []byte 67 | proposal = hexutil.Bytes(proposalBytes) 68 | } 69 | } else { 70 | myType = append(myType, eip712.Type{ 71 | Name: "proposal", 72 | Type: "string", 73 | }) 74 | proposal = v.Proposal 75 | } 76 | 77 | // vote type, vote choice and vote privacy (TODO) 78 | // choice needs to be converted into its native format for envelope 79 | var choice interface{} 80 | // The space between [1, 2, 3] does not matter since we parse it 81 | // hmy governance vote-proposal \ 82 | // --space harmony-mainnet.eth \ 83 | // --proposal 0xTruncated \ 84 | // --proposal-type {"approval","ranked-choice"} \ 85 | // --choice "[1, 2, 3]" \ 86 | // --key 87 | if v.ProposalType == "approval" || v.ProposalType == "ranked-choice" { 88 | myType = append(myType, eip712.Type{ 89 | Name: "choice", 90 | Type: "uint32[]", 91 | }) 92 | var is []int64 93 | if err := json.Unmarshal([]byte(v.Choice), &is); err == nil { 94 | local := make([]interface{}, len(is)) 95 | for i := range is { 96 | local[i] = math.NewHexOrDecimal256(is[i]) 97 | } 98 | choice = local 99 | } else { 100 | return nil, errors.Wrapf(err, 101 | "unexpected value of choice %s (expected uint32[])", choice, 102 | ) 103 | } 104 | // The space between --choice {value} does not matter to snapshot.org 105 | // But for comparing with the snapshot-js library, remove it 106 | // hmy governance vote-proposal \ 107 | // --space harmony-mainnet.eth \ 108 | // --proposal 0xTruncated \ 109 | // # either quadratic or weighted 110 | // --proposal-type {"quadratic","weighted"} \ 111 | // # 20, 20, 40 of my vote (total 80) goes to 1, 2, 3 - note the single / double quotes 112 | // --choice '{"1":20,"2":20,"3":40}' \ 113 | // --key 114 | } else if v.ProposalType == "quadratic" || v.ProposalType == "weighted" { 115 | myType = append(myType, eip712.Type{ 116 | Name: "choice", 117 | Type: "string", 118 | }) 119 | choice = v.Choice 120 | // TODO Untested 121 | // hmy governance vote-proposal \ 122 | // --space harmony-mainnet.eth \ 123 | // --proposal 0xTruncated \ 124 | // --proposal-type ANY \ 125 | // --choice "unknown-format" \ 126 | // --key 127 | // --privacy shutter 128 | // } else if v.Privacy == "shutter" { 129 | // myType = append(myType, eip712.Type{ 130 | // Name: "choice", 131 | // Type: "string", 132 | // }) 133 | // choice = v.Choice 134 | // hmy governance vote-proposal \ 135 | // --space harmony-mainnet.eth \ 136 | // --proposal 0xTruncated \ 137 | // --proposal-type single-choice \ 138 | // --choice 1 \ 139 | // --key 140 | } else if v.ProposalType == "single-choice" { 141 | myType = append(myType, eip712.Type{ 142 | Name: "choice", 143 | Type: "uint32", 144 | }) 145 | if x, err := strconv.Atoi(v.Choice); err != nil { 146 | return nil, errors.Wrapf(err, 147 | "unexpected value of choice %s (expected uint32)", choice, 148 | ) 149 | } else { 150 | choice = math.NewHexOrDecimal256(int64(x)) 151 | } 152 | // hmy governance vote-proposal \ 153 | // --space harmony-mainnet.eth \ 154 | // --proposal 0xTruncated \ 155 | // --proposal-type basic \ 156 | // # any character case works 157 | // --choice {aBstAin/agAiNst/for} \ 158 | // --key 159 | } else if v.ProposalType == "basic" { 160 | myType = append(myType, eip712.Type{ 161 | Name: "choice", 162 | Type: "uint32", 163 | }) 164 | if number, ok := voteToNumberMapping[strings.ToLower(v.Choice)]; ok { 165 | choice = math.NewHexOrDecimal256(number) 166 | } else { 167 | return nil, errors.New( 168 | fmt.Sprintf( 169 | "unknown basic choice %s", 170 | v.Choice, 171 | ), 172 | ) 173 | } 174 | } else { 175 | return nil, errors.New( 176 | fmt.Sprintf( 177 | "unknown proposal type %s", 178 | v.ProposalType, 179 | ), 180 | ) 181 | } 182 | 183 | // order matters so these are added last 184 | myType = append(myType, eip712.Type{ 185 | Name: "reason", 186 | Type: "string", 187 | }) 188 | myType = append(myType, eip712.Type{ 189 | Name: "app", 190 | Type: "string", 191 | }) 192 | // metadata is skipped in this code intentionally 193 | 194 | if v.Timestamp == 0 { 195 | v.Timestamp = time.Now().Unix() 196 | } 197 | 198 | return &TypedData{ 199 | eip712.TypedData{ 200 | Domain: eip712.TypedDataDomain{ 201 | Name: name, 202 | Version: version, 203 | }, 204 | Types: eip712.Types{ 205 | "EIP712Domain": { 206 | { 207 | Name: "name", 208 | Type: "string", 209 | }, 210 | { 211 | Name: "version", 212 | Type: "string", 213 | }, 214 | }, 215 | "Vote": myType, 216 | }, 217 | Message: eip712.TypedDataMessage{ 218 | "from": v.From, 219 | "space": v.Space, 220 | // EncodePrimitiveValue accepts string, float64, or this type 221 | "timestamp": math.NewHexOrDecimal256(v.Timestamp), 222 | "proposal": proposal, 223 | "choice": choice, 224 | "reason": v.Reason, 225 | "app": v.App, 226 | }, 227 | PrimaryType: "Vote", 228 | }, 229 | }, nil 230 | } 231 | -------------------------------------------------------------------------------- /pkg/console/prompt/prompter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package prompt 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/peterh/liner" 24 | ) 25 | 26 | // Stdin holds the stdin line reader (also using stdout for printing prompts). 27 | // Only this reader may be used for input because it keeps an internal buffer. 28 | var Stdin = newTerminalPrompter() 29 | 30 | // UserPrompter defines the methods needed by the console to prompt the user for 31 | // various types of inputs. 32 | type UserPrompter interface { 33 | // PromptInput displays the given prompt to the user and requests some textual 34 | // data to be entered, returning the input of the user. 35 | PromptInput(prompt string) (string, error) 36 | 37 | // PromptPassword displays the given prompt to the user and requests some textual 38 | // data to be entered, but one which must not be echoed out into the terminal. 39 | // The method returns the input provided by the user. 40 | PromptPassword(prompt string) (string, error) 41 | 42 | // PromptConfirm displays the given prompt to the user and requests a boolean 43 | // choice to be made, returning that choice. 44 | PromptConfirm(prompt string) (bool, error) 45 | 46 | // SetHistory sets the input scrollback history that the prompter will allow 47 | // the user to scroll back to. 48 | SetHistory(history []string) 49 | 50 | // AppendHistory appends an entry to the scrollback history. It should be called 51 | // if and only if the prompt to append was a valid command. 52 | AppendHistory(command string) 53 | 54 | // ClearHistory clears the entire history 55 | ClearHistory() 56 | 57 | // SetWordCompleter sets the completion function that the prompter will call to 58 | // fetch completion candidates when the user presses tab. 59 | SetWordCompleter(completer WordCompleter) 60 | } 61 | 62 | // WordCompleter takes the currently edited line with the cursor position and 63 | // returns the completion candidates for the partial word to be completed. If 64 | // the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, 65 | // wo!!!", 9) is passed to the completer which may returns ("Hello, ", {"world", 66 | // "Word"}, "!!!") to have "Hello, world!!!". 67 | type WordCompleter func(line string, pos int) (string, []string, string) 68 | 69 | // terminalPrompter is a UserPrompter backed by the liner package. It supports 70 | // prompting the user for various input, among others for non-echoing password 71 | // input. 72 | type terminalPrompter struct { 73 | *liner.State 74 | warned bool 75 | supported bool 76 | normalMode liner.ModeApplier 77 | rawMode liner.ModeApplier 78 | } 79 | 80 | // newTerminalPrompter creates a liner based user input prompter working off the 81 | // standard input and output streams. 82 | func newTerminalPrompter() *terminalPrompter { 83 | p := new(terminalPrompter) 84 | // Get the original mode before calling NewLiner. 85 | // This is usually regular "cooked" mode where characters echo. 86 | normalMode, _ := liner.TerminalMode() 87 | // Turn on liner. It switches to raw mode. 88 | p.State = liner.NewLiner() 89 | rawMode, err := liner.TerminalMode() 90 | if err != nil || !liner.TerminalSupported() { 91 | p.supported = false 92 | } else { 93 | p.supported = true 94 | p.normalMode = normalMode 95 | p.rawMode = rawMode 96 | // Switch back to normal mode while we're not prompting. 97 | normalMode.ApplyMode() 98 | } 99 | p.SetCtrlCAborts(true) 100 | p.SetTabCompletionStyle(liner.TabPrints) 101 | p.SetMultiLineMode(true) 102 | return p 103 | } 104 | 105 | // PromptInput displays the given prompt to the user and requests some textual 106 | // data to be entered, returning the input of the user. 107 | func (p *terminalPrompter) PromptInput(prompt string) (string, error) { 108 | if p.supported { 109 | p.rawMode.ApplyMode() 110 | defer p.normalMode.ApplyMode() 111 | } else { 112 | // liner tries to be smart about printing the prompt 113 | // and doesn't print anything if input is redirected. 114 | // Un-smart it by printing the prompt always. 115 | fmt.Print(prompt) 116 | prompt = "" 117 | defer fmt.Println() 118 | } 119 | return p.State.Prompt(prompt) 120 | } 121 | 122 | // PromptPassword displays the given prompt to the user and requests some textual 123 | // data to be entered, but one which must not be echoed out into the terminal. 124 | // The method returns the input provided by the user. 125 | func (p *terminalPrompter) PromptPassword(prompt string) (passwd string, err error) { 126 | if p.supported { 127 | p.rawMode.ApplyMode() 128 | defer p.normalMode.ApplyMode() 129 | return p.State.PasswordPrompt(prompt) 130 | } 131 | if !p.warned { 132 | fmt.Println("!! Unsupported terminal, password will be echoed.") 133 | p.warned = true 134 | } 135 | // Just as in Prompt, handle printing the prompt here instead of relying on liner. 136 | fmt.Print(prompt) 137 | passwd, err = p.State.Prompt("") 138 | fmt.Println() 139 | return passwd, err 140 | } 141 | 142 | // PromptConfirm displays the given prompt to the user and requests a boolean 143 | // choice to be made, returning that choice. 144 | func (p *terminalPrompter) PromptConfirm(prompt string) (bool, error) { 145 | input, err := p.Prompt(prompt + " [y/n] ") 146 | if len(input) > 0 && strings.ToUpper(input[:1]) == "Y" { 147 | return true, nil 148 | } 149 | return false, err 150 | } 151 | 152 | // SetHistory sets the input scrollback history that the prompter will allow 153 | // the user to scroll back to. 154 | func (p *terminalPrompter) SetHistory(history []string) { 155 | p.State.ReadHistory(strings.NewReader(strings.Join(history, "\n"))) 156 | } 157 | 158 | // AppendHistory appends an entry to the scrollback history. 159 | func (p *terminalPrompter) AppendHistory(command string) { 160 | p.State.AppendHistory(command) 161 | } 162 | 163 | // ClearHistory clears the entire history 164 | func (p *terminalPrompter) ClearHistory() { 165 | p.State.ClearHistory() 166 | } 167 | 168 | // SetWordCompleter sets the completion function that the prompter will call to 169 | // fetch completion candidates when the user presses tab. 170 | func (p *terminalPrompter) SetWordCompleter(completer WordCompleter) { 171 | p.State.SetWordCompleter(liner.WordCompleter(completer)) 172 | } 173 | -------------------------------------------------------------------------------- /pkg/ledger/nano_S_driver.go: -------------------------------------------------------------------------------- 1 | package ledger 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "errors" 8 | "fmt" 9 | "io" 10 | 11 | "github.com/karalabe/usb" 12 | ) 13 | 14 | const ( 15 | signatureSize int = 65 16 | packetSize int = 255 17 | ) 18 | 19 | var DEBUG bool 20 | 21 | type hidFramer struct { 22 | rw io.ReadWriter 23 | seq uint16 24 | buf [64]byte 25 | pos int 26 | } 27 | 28 | type APDU struct { 29 | CLA byte 30 | INS byte 31 | P1, P2 byte 32 | Payload []byte 33 | } 34 | 35 | type apduFramer struct { 36 | hf *hidFramer 37 | buf [2]byte // to read APDU length prefix 38 | } 39 | 40 | type NanoS struct { 41 | device *apduFramer 42 | } 43 | 44 | type ErrCode uint16 45 | 46 | func (hf *hidFramer) Reset() { 47 | hf.seq = 0 48 | } 49 | 50 | func (hf *hidFramer) Write(p []byte) (int, error) { 51 | if DEBUG { 52 | fmt.Println("HID <=", hex.EncodeToString(p)) 53 | } 54 | // split into 64-byte chunks 55 | chunk := make([]byte, 64) 56 | binary.BigEndian.PutUint16(chunk[:2], 0x0101) 57 | chunk[2] = 0x05 58 | var seq uint16 59 | buf := new(bytes.Buffer) 60 | binary.Write(buf, binary.BigEndian, uint16(len(p))) 61 | buf.Write(p) 62 | for buf.Len() > 0 { 63 | binary.BigEndian.PutUint16(chunk[3:5], seq) 64 | n, _ := buf.Read(chunk[5:]) 65 | if n, err := hf.rw.Write(chunk[:5+n]); err != nil { 66 | return n, err 67 | } 68 | seq++ 69 | } 70 | return len(p), nil 71 | } 72 | 73 | func (hf *hidFramer) Read(p []byte) (int, error) { 74 | if hf.seq > 0 && hf.pos != 64 { 75 | // drain buf 76 | n := copy(p, hf.buf[hf.pos:]) 77 | hf.pos += n 78 | return n, nil 79 | } 80 | // read next 64-byte packet 81 | if n, err := hf.rw.Read(hf.buf[:]); err != nil { 82 | return 0, err 83 | } else if n != 64 { 84 | panic("read less than 64 bytes from HID") 85 | } 86 | // parse header 87 | channelID := binary.BigEndian.Uint16(hf.buf[:2]) 88 | commandTag := hf.buf[2] 89 | seq := binary.BigEndian.Uint16(hf.buf[3:5]) 90 | if channelID != 0x0101 { 91 | return 0, fmt.Errorf("bad channel ID 0x%x", channelID) 92 | } else if commandTag != 0x05 { 93 | return 0, fmt.Errorf("bad command tag 0x%x", commandTag) 94 | } else if seq != hf.seq { 95 | return 0, fmt.Errorf("bad sequence number %v (expected %v)", seq, hf.seq) 96 | } 97 | hf.seq++ 98 | // start filling p 99 | n := copy(p, hf.buf[5:]) 100 | hf.pos = 5 + n 101 | return n, nil 102 | } 103 | 104 | func (af *apduFramer) Exchange(apdu APDU) ([]byte, error) { 105 | if len(apdu.Payload) > packetSize { 106 | panic("APDU payload cannot exceed 255 bytes") 107 | } 108 | af.hf.Reset() 109 | data := append([]byte{ 110 | apdu.CLA, 111 | apdu.INS, 112 | apdu.P1, apdu.P2, 113 | byte(len(apdu.Payload)), 114 | }, apdu.Payload...) 115 | if _, err := af.hf.Write(data); err != nil { 116 | return nil, err 117 | } 118 | 119 | // read APDU length 120 | if _, err := io.ReadFull(af.hf, af.buf[:]); err != nil { 121 | return nil, err 122 | } 123 | // read APDU payload 124 | respLen := binary.BigEndian.Uint16(af.buf[:2]) 125 | resp := make([]byte, respLen) 126 | _, err := io.ReadFull(af.hf, resp) 127 | if DEBUG { 128 | fmt.Println("HID =>", hex.EncodeToString(resp)) 129 | } 130 | return resp, err 131 | } 132 | 133 | func (c ErrCode) Error() string { 134 | return fmt.Sprintf("Error code 0x%x", uint16(c)) 135 | } 136 | 137 | const codeSuccess = 0x9000 138 | const codeUserRejected = 0x6985 139 | const codeInvalidParam = 0x6b01 140 | 141 | var errUserRejected = errors.New("user denied request") 142 | var errInvalidParam = errors.New("invalid request parameters") 143 | 144 | func (n *NanoS) Exchange(cmd byte, p1, p2 byte, data []byte) (resp []byte, err error) { 145 | resp, err = n.device.Exchange(APDU{ 146 | CLA: 0xe0, 147 | INS: cmd, 148 | P1: p1, 149 | P2: p2, 150 | Payload: data, 151 | }) 152 | if err != nil { 153 | return nil, err 154 | } else if len(resp) < 2 { 155 | return nil, errors.New("APDU response missing status code") 156 | } 157 | code := binary.BigEndian.Uint16(resp[len(resp)-2:]) 158 | resp = resp[:len(resp)-2] 159 | switch code { 160 | case codeSuccess: 161 | err = nil 162 | case codeUserRejected: 163 | err = errUserRejected 164 | case codeInvalidParam: 165 | err = errInvalidParam 166 | default: 167 | err = ErrCode(code) 168 | } 169 | return 170 | } 171 | 172 | const ( 173 | cmdGetVersion = 0x01 174 | cmdGetPublicKey = 0x02 175 | cmdSignStaking = 0x04 176 | cmdSignTx = 0x08 177 | 178 | p1First = 0x0 179 | p1More = 0x80 180 | 181 | p2DisplayAddress = 0x00 182 | p2DisplayHash = 0x00 183 | p2SignHash = 0x01 184 | p2Finish = 0x02 185 | ) 186 | 187 | func (n *NanoS) GetVersion() (version string, err error) { 188 | resp, err := n.Exchange(cmdGetVersion, 0, 0, nil) 189 | if err != nil { 190 | return "", err 191 | } else if len(resp) != 3 { 192 | return "", errors.New("version has wrong length") 193 | } 194 | return fmt.Sprintf("v%d.%d.%d", resp[0], resp[1], resp[2]), nil 195 | } 196 | 197 | func (n *NanoS) GetAddress() (oneAddr string, err error) { 198 | resp, err := n.Exchange(cmdGetPublicKey, 0, p2DisplayAddress, []byte{}) 199 | if err != nil { 200 | return "", err 201 | } 202 | 203 | var pubkey [42]byte 204 | if copy(pubkey[:], resp) != len(pubkey) { 205 | return "", errors.New("pubkey has wrong length") 206 | } 207 | return string(pubkey[:]), nil 208 | } 209 | 210 | func (n *NanoS) SignTxn(txn []byte) (sig [signatureSize]byte, err error) { 211 | buf := bytes.NewBuffer(txn) 212 | var resp []byte 213 | 214 | for buf.Len() > 0 { 215 | var p1 byte = p1More 216 | var p2 byte = p2SignHash 217 | if resp == nil { 218 | p1 = p1First 219 | } 220 | if buf.Len() < packetSize { 221 | p2 = p2Finish 222 | } 223 | resp, err = n.Exchange(cmdSignTx, p1, p2, buf.Next(packetSize)) 224 | if err != nil { 225 | return [signatureSize]byte{}, err 226 | } 227 | } 228 | 229 | copy(sig[:], resp) 230 | 231 | if copy(sig[:], resp) != len(sig) { 232 | return [signatureSize]byte{}, errors.New("signature has wrong length") 233 | } 234 | return 235 | } 236 | 237 | func (n *NanoS) SignStaking(stake []byte) (sig [signatureSize]byte, err error) { 238 | buf := bytes.NewBuffer(stake) 239 | var resp []byte 240 | 241 | for buf.Len() > 0 { 242 | var p1 byte = p1More 243 | var p2 byte = p2SignHash 244 | if resp == nil { 245 | p1 = p1First 246 | } 247 | 248 | if buf.Len() < packetSize { 249 | p2 = p2Finish 250 | } 251 | 252 | resp, err = n.Exchange(cmdSignStaking, p1, p2, buf.Next(packetSize)) 253 | if err != nil { 254 | return [signatureSize]byte{}, err 255 | } 256 | } 257 | 258 | copy(sig[:], resp) 259 | 260 | if copy(sig[:], resp) != len(sig) { 261 | return [signatureSize]byte{}, errors.New("signature has wrong length") 262 | } 263 | return 264 | } 265 | 266 | func OpenNanoS() (*NanoS, error) { 267 | const ( 268 | ledgerVendorID = 0x2c97 269 | // new device ID for firmware 1.6.0 270 | ledgerNanoSProductID = 0x1011 271 | // ledgerNanoSProductID = 0x0001 272 | //ledgerUsageID = 0xffa0 273 | ) 274 | 275 | // search for Nano S 276 | devices, err := usb.EnumerateHid(ledgerVendorID, ledgerNanoSProductID) 277 | if err != nil { 278 | return nil, err 279 | } 280 | if len(devices) == 0 { 281 | return nil, errors.New("nano S not detected") 282 | } else if len(devices) > 1 { 283 | return nil, errors.New("detected multiple Nano S devices") 284 | } 285 | 286 | // open the device 287 | device, err := devices[0].Open() 288 | if err != nil { 289 | return nil, err 290 | } 291 | 292 | // wrap raw device I/O in HID+APDU protocols 293 | return &NanoS{ 294 | device: &apduFramer{ 295 | hf: &hidFramer{ 296 | rw: device, 297 | }, 298 | }, 299 | }, nil 300 | } 301 | -------------------------------------------------------------------------------- /cmd/subcommands/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/harmony-one/go-sdk/pkg/address" 8 | "net/http" 9 | "os" 10 | "path" 11 | "regexp" 12 | "strings" 13 | 14 | color "github.com/fatih/color" 15 | "github.com/harmony-one/go-sdk/pkg/common" 16 | "github.com/harmony-one/go-sdk/pkg/rpc" 17 | rpcEth "github.com/harmony-one/go-sdk/pkg/rpc/eth" 18 | rpcV1 "github.com/harmony-one/go-sdk/pkg/rpc/v1" 19 | "github.com/harmony-one/go-sdk/pkg/sharding" 20 | "github.com/harmony-one/go-sdk/pkg/store" 21 | "github.com/pkg/errors" 22 | "github.com/spf13/cobra" 23 | "github.com/spf13/cobra/doc" 24 | ) 25 | 26 | var ( 27 | verbose bool 28 | useLedgerWallet bool 29 | noLatest bool 30 | noPrettyOutput bool 31 | node string 32 | rpcPrefix string 33 | keyStoreDir string 34 | givenFilePath string 35 | endpoint = regexp.MustCompile(`https://api\.s[0-9]\..*\.hmny\.io`) 36 | request = func(method string, params []interface{}) error { 37 | if !noLatest { 38 | params = append(params, "latest") 39 | } 40 | success, failure := rpc.Request(method, node, params) 41 | if failure != nil { 42 | return failure 43 | } 44 | asJSON, _ := json.Marshal(success) 45 | if noPrettyOutput { 46 | fmt.Println(string(asJSON)) 47 | return nil 48 | } 49 | fmt.Println(common.JSONPrettyFormat(string(asJSON))) 50 | return nil 51 | } 52 | // RootCmd is single entry point of the CLI 53 | RootCmd = &cobra.Command{ 54 | Use: "hmy", 55 | Short: "Harmony blockchain", 56 | SilenceUsage: true, 57 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 58 | if verbose { 59 | common.EnableAllVerbose() 60 | } 61 | switch rpcPrefix { 62 | case "hmy": 63 | rpc.Method = rpcV1.Method 64 | case "eth": 65 | rpc.Method = rpcEth.Method 66 | default: 67 | rpc.Method = rpcV1.Method 68 | } 69 | if strings.HasPrefix(node, "https://") || strings.HasPrefix(node, "http://") || 70 | strings.HasPrefix(node, "ws://") { 71 | //No op, already has protocol, respect protocol default ports. 72 | } else if strings.HasPrefix(node, "api") || strings.HasPrefix(node, "ws") { 73 | node = "https://" + node 74 | } else { 75 | switch URLcomponents := strings.Split(node, ":"); len(URLcomponents) { 76 | case 1: 77 | node = "http://" + node + ":9500" 78 | case 2: 79 | node = "http://" + node 80 | default: 81 | node = node 82 | } 83 | } 84 | 85 | if targetChain == "" { 86 | if node == defaultNodeAddr { 87 | routes, err := sharding.Structure(node) 88 | if err != nil { 89 | chainName = chainIDWrapper{chainID: &common.Chain.TestNet} 90 | } else { 91 | if len(routes) == 0 { 92 | return errors.New("empty reply from sharding structure") 93 | } 94 | chainName = endpointToChainID(routes[0].HTTP) 95 | } 96 | } else if endpoint.Match([]byte(node)) { 97 | chainName = endpointToChainID(node) 98 | } else if strings.Contains(node, "api.harmony.one") { 99 | chainName = chainIDWrapper{chainID: &common.Chain.MainNet} 100 | } else { 101 | chainName = chainIDWrapper{chainID: &common.Chain.TestNet} 102 | } 103 | } else { 104 | chain, err := common.StringToChainID(targetChain) 105 | if err != nil { 106 | return err 107 | } 108 | chainName = chainIDWrapper{chainID: chain} 109 | } 110 | 111 | return nil 112 | }, 113 | Long: fmt.Sprintf(` 114 | CLI interface to the Harmony blockchain 115 | 116 | %s`, g("Invoke 'hmy cookbook' for examples of the most common, important usages")), 117 | RunE: func(cmd *cobra.Command, args []string) error { 118 | cmd.Help() 119 | return nil 120 | }, 121 | } 122 | ) 123 | 124 | func init() { 125 | vS := "dump out debug information, same as env var HMY_ALL_DEBUG=true" 126 | RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, vS) 127 | RootCmd.PersistentFlags().StringVarP(&node, "node", "n", defaultNodeAddr, "") 128 | RootCmd.PersistentFlags().StringVarP(&rpcPrefix, "rpc-prefix", "r", defaultRpcPrefix, "") 129 | RootCmd.PersistentFlags().BoolVar( 130 | &noLatest, "no-latest", false, "Do not add 'latest' to RPC params", 131 | ) 132 | RootCmd.PersistentFlags().BoolVar( 133 | &noPrettyOutput, "no-pretty", false, "Disable pretty print JSON outputs", 134 | ) 135 | RootCmd.AddCommand(&cobra.Command{ 136 | Use: "cookbook", 137 | Short: "Example usages of the most important, frequently used commands", 138 | RunE: func(cmd *cobra.Command, args []string) error { 139 | var docNode, docNet string 140 | if node == defaultNodeAddr || chainName.chainID == &common.Chain.MainNet { 141 | docNode = `https://api.s0.t.hmny.io` 142 | docNet = `Mainnet` 143 | } else if chainName.chainID == &common.Chain.TestNet { 144 | docNode = `https://api.s0.b.hmny.io` 145 | docNet = `Long-Running Testnet` 146 | } else if chainName.chainID == &common.Chain.PangaeaNet { 147 | docNode = `https://api.s0.os.hmny.io` 148 | docNet = `Open Staking Network` 149 | } else if chainName.chainID == &common.Chain.PartnerNet { 150 | docNode = `https://api.s0.ps.hmny.io` 151 | docNet = `Partner Testnet` 152 | } else if chainName.chainID == &common.Chain.StressNet { 153 | docNode = `https://api.s0.stn.hmny.io` 154 | docNet = `Stress Testing Network` 155 | } 156 | fmt.Print(strings.ReplaceAll(strings.ReplaceAll(cookbookDoc, `[NODE]`, docNode), `[NETWORK]`, docNet)) 157 | return nil 158 | }, 159 | }) 160 | RootCmd.PersistentFlags().BoolVarP(&useLedgerWallet, "ledger", "e", false, "Use ledger hardware wallet") 161 | RootCmd.PersistentFlags().StringVar(&givenFilePath, "file", "", "Path to file for given command when applicable") 162 | RootCmd.AddCommand(&cobra.Command{ 163 | Use: "docs", 164 | Short: fmt.Sprintf("Generate docs to a local %s directory", hmyDocsDir), 165 | RunE: func(cmd *cobra.Command, args []string) error { 166 | cwd, _ := os.Getwd() 167 | docDir := path.Join(cwd, hmyDocsDir) 168 | os.Mkdir(docDir, 0700) 169 | doc.GenMarkdownTree(RootCmd, docDir) 170 | return nil 171 | }, 172 | }) 173 | } 174 | 175 | var ( 176 | // VersionWrapDump meant to be set from main.go 177 | VersionWrapDump = "" 178 | cookbook = color.GreenString("hmy cookbook") 179 | versionLink = "https://harmony.one/hmycli_ver" 180 | versionFormat = regexp.MustCompile("v[0-9]+-[a-z0-9]{7}") 181 | ) 182 | 183 | // Execute kicks off the hmy CLI 184 | func Execute() { 185 | RootCmd.SilenceErrors = true 186 | if err := RootCmd.Execute(); err != nil { 187 | resp, httpErr := http.Get(versionLink) 188 | if httpErr != nil { 189 | return 190 | } 191 | defer resp.Body.Close() 192 | // If error, no op 193 | if resp != nil && resp.StatusCode == 200 { 194 | buf := new(bytes.Buffer) 195 | buf.ReadFrom(resp.Body) 196 | 197 | currentVersion := versionFormat.FindAllString(buf.String(), 1) 198 | if currentVersion != nil && currentVersion[0] != VersionWrapDump { 199 | warnMsg := fmt.Sprintf("Warning: Using outdated version. Redownload to upgrade to %s\n", currentVersion[0]) 200 | fmt.Fprintf(os.Stderr, color.RedString(warnMsg)) 201 | } 202 | } 203 | errMsg := errors.Wrapf(err, "commit: %s, error", VersionWrapDump).Error() 204 | fmt.Fprintf(os.Stderr, errMsg+"\n") 205 | fmt.Fprintf(os.Stderr, "check "+cookbook+" for valid examples or try adding a `--help` flag\n") 206 | os.Exit(1) 207 | } 208 | } 209 | 210 | func endpointToChainID(nodeAddr string) chainIDWrapper { 211 | if strings.Contains(nodeAddr, ".t.") { 212 | return chainIDWrapper{chainID: &common.Chain.MainNet} 213 | } else if strings.Contains(nodeAddr, ".b.") { 214 | return chainIDWrapper{chainID: &common.Chain.TestNet} 215 | } else if strings.Contains(nodeAddr, ".os.") { 216 | return chainIDWrapper{chainID: &common.Chain.PangaeaNet} 217 | } else if strings.Contains(nodeAddr, ".ps.") { 218 | return chainIDWrapper{chainID: &common.Chain.PartnerNet} 219 | } else if strings.Contains(nodeAddr, ".stn.") { 220 | return chainIDWrapper{chainID: &common.Chain.StressNet} 221 | } else if strings.Contains(nodeAddr, ".dry.") { 222 | return chainIDWrapper{chainID: &common.Chain.MainNet} 223 | } 224 | return chainIDWrapper{chainID: &common.Chain.TestNet} 225 | } 226 | 227 | func validateAddress(cmd *cobra.Command, args []string) error { 228 | // Check if input valid one address 229 | tmpAddr := oneAddress{} 230 | if err := tmpAddr.Set(args[0]); err != nil { 231 | // Check if input is valid account name 232 | if acc, err := store.AddressFromAccountName(args[0]); err == nil { 233 | addr = oneAddress{acc} 234 | return nil 235 | } 236 | 237 | bech32Addr := address.ToBech32(address.Parse(args[0])) 238 | if bech32Addr == "" { 239 | return fmt.Errorf("Invalid one address/Invalid account name: %s", args[0]) 240 | } 241 | 242 | tmpAddr = oneAddress{bech32Addr} 243 | } 244 | addr = tmpAddr 245 | return nil 246 | } 247 | -------------------------------------------------------------------------------- /pkg/rpc/methods.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | rpcCommon "github.com/harmony-one/go-sdk/pkg/rpc/common" 7 | rpcV1 "github.com/harmony-one/go-sdk/pkg/rpc/v1" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | var ( 12 | RPCPrefix = "hmy" 13 | Method rpcCommon.RpcEnumList = rpcV1.Method 14 | ) 15 | 16 | type RpcEnumList struct { 17 | GetShardingStructure rpcCommon.RpcMethod 18 | GetBlockByHash rpcCommon.RpcMethod 19 | GetBlockByNumber rpcCommon.RpcMethod 20 | GetBlockTransactionCountByHash rpcCommon.RpcMethod 21 | GetBlockTransactionCountByNumber rpcCommon.RpcMethod 22 | GetCode rpcCommon.RpcMethod 23 | GetTransactionByBlockHashAndIndex rpcCommon.RpcMethod 24 | GetTransactionByBlockNumberAndIndex rpcCommon.RpcMethod 25 | GetTransactionByHash rpcCommon.RpcMethod 26 | GetStakingTransactionByHash rpcCommon.RpcMethod 27 | GetTransactionReceipt rpcCommon.RpcMethod 28 | Syncing rpcCommon.RpcMethod 29 | PeerCount rpcCommon.RpcMethod 30 | GetBalance rpcCommon.RpcMethod 31 | GetStorageAt rpcCommon.RpcMethod 32 | GetTransactionCount rpcCommon.RpcMethod 33 | SendTransaction rpcCommon.RpcMethod 34 | SendRawTransaction rpcCommon.RpcMethod 35 | Subscribe rpcCommon.RpcMethod 36 | GetPastLogs rpcCommon.RpcMethod 37 | GetWork rpcCommon.RpcMethod 38 | GetProof rpcCommon.RpcMethod 39 | GetFilterChanges rpcCommon.RpcMethod 40 | NewPendingTransactionFilter rpcCommon.RpcMethod 41 | NewBlockFilter rpcCommon.RpcMethod 42 | NewFilter rpcCommon.RpcMethod 43 | Call rpcCommon.RpcMethod 44 | EstimateGas rpcCommon.RpcMethod 45 | GasPrice rpcCommon.RpcMethod 46 | BlockNumber rpcCommon.RpcMethod 47 | UnSubscribe rpcCommon.RpcMethod 48 | NetVersion rpcCommon.RpcMethod 49 | ProtocolVersion rpcCommon.RpcMethod 50 | GetNodeMetadata rpcCommon.RpcMethod 51 | GetLatestBlockHeader rpcCommon.RpcMethod 52 | SendRawStakingTransaction rpcCommon.RpcMethod 53 | GetElectedValidatorAddresses rpcCommon.RpcMethod 54 | GetAllValidatorAddresses rpcCommon.RpcMethod 55 | GetValidatorInformation rpcCommon.RpcMethod 56 | GetAllValidatorInformation rpcCommon.RpcMethod 57 | GetValidatorInformationByBlockNumber rpcCommon.RpcMethod 58 | GetAllValidatorInformationByBlockNumber rpcCommon.RpcMethod 59 | GetDelegationsByDelegator rpcCommon.RpcMethod 60 | GetDelegationsByValidator rpcCommon.RpcMethod 61 | GetCurrentTransactionErrorSink rpcCommon.RpcMethod 62 | GetMedianRawStakeSnapshot rpcCommon.RpcMethod 63 | GetCurrentStakingErrorSink rpcCommon.RpcMethod 64 | GetTransactionsHistory rpcCommon.RpcMethod 65 | GetPendingTxnsInPool rpcCommon.RpcMethod 66 | GetPendingCrosslinks rpcCommon.RpcMethod 67 | GetPendingCXReceipts rpcCommon.RpcMethod 68 | GetCurrentUtilityMetrics rpcCommon.RpcMethod 69 | ResendCX rpcCommon.RpcMethod 70 | GetSuperCommmittees rpcCommon.RpcMethod 71 | GetCurrentBadBlocks rpcCommon.RpcMethod 72 | GetShardID rpcCommon.RpcMethod 73 | GetLastCrossLinks rpcCommon.RpcMethod 74 | GetLatestChainHeaders rpcCommon.RpcMethod 75 | } 76 | 77 | type errorCode int 78 | type rpcErrorCodeList struct { 79 | rpcInvalidRequest errorCode 80 | rpcMethodNotFound errorCode 81 | rpcInvalidParams errorCode 82 | rpcInternalError errorCode 83 | rpcParseError errorCode 84 | rpcMiscError errorCode 85 | rpcTypeError errorCode 86 | rpcInvalidAddressOrKey errorCode 87 | rpcInvalidParameter errorCode 88 | rpcDatabaseError errorCode 89 | rpcDeserializationError errorCode 90 | rpcVerifyError errorCode 91 | rpcVerifyRejected errorCode 92 | rpcInWarmup errorCode 93 | rpcMethodDeprecated errorCode 94 | rpcGenericError errorCode 95 | } 96 | 97 | // TODO Do not punt on the field names 98 | var errorCodeEnumeration = rpcErrorCodeList{ 99 | // Standard JSON-RPC 2.0 errors 100 | // RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400). 101 | // It should not be used for application-layer errors. 102 | rpcInvalidRequest: -32600, 103 | // RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404). 104 | // It should not be used for application-layer errors. 105 | rpcMethodNotFound: -32601, 106 | rpcInvalidParams: -32602, 107 | // RPC_INTERNAL_ERROR should only be used for genuine errors in bitcoind 108 | // (for example datadir corruption). 109 | rpcInternalError: -32603, 110 | rpcParseError: -32700, 111 | // General application defined errors 112 | rpcMiscError: -1, // std::exception thrown in command handling 113 | rpcTypeError: -3, // Unexpected type was passed as parameter 114 | rpcInvalidAddressOrKey: -5, // Invalid address or key 115 | rpcInvalidParameter: -8, // Invalid, missing or duplicate parameter 116 | rpcDatabaseError: -20, // Database error 117 | rpcDeserializationError: -22, // Error parsing or validating structure in raw format 118 | rpcVerifyError: -25, // General error during transaction or block submission 119 | rpcVerifyRejected: -26, // Transaction or block was rejected by network rules 120 | rpcInWarmup: -28, // Client still warming up 121 | rpcMethodDeprecated: -32, // RPC method is deprecated 122 | rpcGenericError: -32000, // Generic catchall error code 123 | } 124 | 125 | const ( 126 | invalidRequestError = "Invalid Request object" 127 | methodNotFoundError = "Method not found" 128 | invalidParamsError = "Invalid method parameter(s)" 129 | internalError = "Internal JSON-RPC error" 130 | parseError = "Error while parsing the JSON text" 131 | miscError = "std::exception thrown in command handling" 132 | typeError = "Unexpected type was passed as parameter" 133 | invalidAddressOrKeyError = "Invalid address or key" 134 | invalidParameterError = "Invalid, missing or duplicate parameter" 135 | databaseError = "Database error" 136 | deserializationError = "Error parsing or validating structure in raw format" 137 | verifyError = "General error during transaction or block submission" 138 | verifyRejectedError = "Transaction or block was rejected by network rules" 139 | rpcInWarmupError = "Client still warming up" 140 | methodDeprecatedError = "RPC method deprecated" 141 | catchAllError = "Catch all RPC error" 142 | ) 143 | 144 | // ErrorCodeToError lifts an untyped error code from RPC to Error value 145 | func ErrorCodeToError(message string, code float64) error { 146 | return errors.Wrap(errors.New(message), codeToMessage(code)) 147 | } 148 | 149 | // TODO Use reflection here instead of typing out the cases or at least a map 150 | func codeToMessage(err float64) string { 151 | switch e := errorCode(err); e { 152 | case errorCodeEnumeration.rpcInvalidRequest: 153 | return invalidRequestError 154 | case errorCodeEnumeration.rpcMethodNotFound: 155 | return methodNotFoundError 156 | case errorCodeEnumeration.rpcInvalidParams: 157 | return invalidParamsError 158 | case errorCodeEnumeration.rpcInternalError: 159 | return internalError 160 | case errorCodeEnumeration.rpcParseError: 161 | return parseError 162 | case errorCodeEnumeration.rpcMiscError: 163 | return miscError 164 | case errorCodeEnumeration.rpcTypeError: 165 | return typeError 166 | case errorCodeEnumeration.rpcInvalidAddressOrKey: 167 | return invalidAddressOrKeyError 168 | case errorCodeEnumeration.rpcInvalidParameter: 169 | return invalidParameterError 170 | case errorCodeEnumeration.rpcDatabaseError: 171 | return databaseError 172 | case errorCodeEnumeration.rpcDeserializationError: 173 | return deserializationError 174 | case errorCodeEnumeration.rpcVerifyError: 175 | return verifyError 176 | case errorCodeEnumeration.rpcVerifyRejected: 177 | return verifyRejectedError 178 | case errorCodeEnumeration.rpcInWarmup: 179 | return rpcInWarmupError 180 | case errorCodeEnumeration.rpcMethodDeprecated: 181 | return methodDeprecatedError 182 | case errorCodeEnumeration.rpcGenericError: 183 | return catchAllError 184 | default: 185 | return fmt.Sprintf("Error number %v not found", err) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pkg/console/jsre/pretty.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package jsre 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "reflect" 23 | "sort" 24 | "strconv" 25 | "strings" 26 | 27 | "github.com/dop251/goja" 28 | "github.com/fatih/color" 29 | ) 30 | 31 | const ( 32 | maxPrettyPrintLevel = 3 33 | indentString = " " 34 | ) 35 | 36 | var ( 37 | FunctionColor = color.New(color.FgMagenta).SprintfFunc() 38 | SpecialColor = color.New(color.Bold).SprintfFunc() 39 | NumberColor = color.New(color.FgRed).SprintfFunc() 40 | StringColor = color.New(color.FgGreen).SprintfFunc() 41 | ErrorColor = color.New(color.FgHiRed).SprintfFunc() 42 | ) 43 | 44 | // these fields are hidden when printing objects. 45 | var boringKeys = map[string]bool{ 46 | "valueOf": true, 47 | "toString": true, 48 | "toLocaleString": true, 49 | "hasOwnProperty": true, 50 | "isPrototypeOf": true, 51 | "propertyIsEnumerable": true, 52 | "constructor": true, 53 | } 54 | 55 | // prettyPrint writes value to standard output. 56 | func prettyPrint(vm *goja.Runtime, value goja.Value, w io.Writer) { 57 | ppctx{vm: vm, w: w}.printValue(value, 0, false) 58 | } 59 | 60 | // prettyError writes err to standard output. 61 | func prettyError(vm *goja.Runtime, err error, w io.Writer) { 62 | failure := err.Error() 63 | if gojaErr, ok := err.(*goja.Exception); ok { 64 | failure = gojaErr.String() 65 | } 66 | fmt.Fprint(w, ErrorColor("%s", failure)) 67 | } 68 | 69 | func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value { 70 | for _, v := range call.Arguments { 71 | prettyPrint(re.vm, v, re.output) 72 | fmt.Fprintln(re.output) 73 | } 74 | return goja.Undefined() 75 | } 76 | 77 | type ppctx struct { 78 | vm *goja.Runtime 79 | w io.Writer 80 | } 81 | 82 | func (ctx ppctx) indent(level int) string { 83 | return strings.Repeat(indentString, level) 84 | } 85 | 86 | func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) { 87 | if goja.IsNull(v) || goja.IsUndefined(v) { 88 | fmt.Fprint(ctx.w, SpecialColor(v.String())) 89 | return 90 | } 91 | kind := v.ExportType().Kind() 92 | switch { 93 | case kind == reflect.Bool: 94 | fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean())) 95 | case kind == reflect.String: 96 | fmt.Fprint(ctx.w, StringColor("%q", v.String())) 97 | case kind >= reflect.Int && kind <= reflect.Complex128: 98 | fmt.Fprint(ctx.w, NumberColor("%s", v.String())) 99 | default: 100 | if obj, ok := v.(*goja.Object); ok { 101 | ctx.printObject(obj, level, inArray) 102 | } else { 103 | fmt.Fprintf(ctx.w, "", v) 104 | } 105 | } 106 | } 107 | 108 | // SafeGet attempt to get the value associated to `key`, and 109 | // catches the panic that goja creates if an error occurs in 110 | // key. 111 | func SafeGet(obj *goja.Object, key string) (ret goja.Value) { 112 | defer func() { 113 | if r := recover(); r != nil { 114 | ret = goja.Undefined() 115 | } 116 | }() 117 | ret = obj.Get(key) 118 | 119 | return ret 120 | } 121 | 122 | func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) { 123 | switch obj.ClassName() { 124 | case "Array", "GoArray": 125 | lv := obj.Get("length") 126 | len := lv.ToInteger() 127 | if len == 0 { 128 | fmt.Fprintf(ctx.w, "[]") 129 | return 130 | } 131 | if level > maxPrettyPrintLevel { 132 | fmt.Fprint(ctx.w, "[...]") 133 | return 134 | } 135 | fmt.Fprint(ctx.w, "[") 136 | for i := int64(0); i < len; i++ { 137 | el := obj.Get(strconv.FormatInt(i, 10)) 138 | if el != nil { 139 | ctx.printValue(el, level+1, true) 140 | } 141 | if i < len-1 { 142 | fmt.Fprintf(ctx.w, ", ") 143 | } 144 | } 145 | fmt.Fprint(ctx.w, "]") 146 | 147 | case "Object": 148 | // Print values from bignumber.js as regular numbers. 149 | if ctx.isBigNumber(obj) { 150 | fmt.Fprint(ctx.w, NumberColor("%s", toString(obj))) 151 | return 152 | } 153 | // Otherwise, print all fields indented, but stop if we're too deep. 154 | keys := ctx.fields(obj) 155 | if len(keys) == 0 { 156 | fmt.Fprint(ctx.w, "{}") 157 | return 158 | } 159 | if level > maxPrettyPrintLevel { 160 | fmt.Fprint(ctx.w, "{...}") 161 | return 162 | } 163 | fmt.Fprintln(ctx.w, "{") 164 | for i, k := range keys { 165 | v := SafeGet(obj, k) 166 | fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) 167 | ctx.printValue(v, level+1, false) 168 | if i < len(keys)-1 { 169 | fmt.Fprintf(ctx.w, ",") 170 | } 171 | fmt.Fprintln(ctx.w) 172 | } 173 | if inArray { 174 | level-- 175 | } 176 | fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) 177 | 178 | case "Function": 179 | robj := obj.ToString() 180 | desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n") 181 | desc = strings.Replace(desc, " (", "(", 1) 182 | fmt.Fprint(ctx.w, FunctionColor("%s", desc)) 183 | 184 | case "RegExp": 185 | fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) 186 | 187 | default: 188 | if level <= maxPrettyPrintLevel { 189 | s := obj.ToString().String() 190 | fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s) 191 | } else { 192 | fmt.Fprintf(ctx.w, "<%s>", obj.ClassName()) 193 | } 194 | } 195 | } 196 | 197 | func (ctx ppctx) fields(obj *goja.Object) []string { 198 | var ( 199 | vals, methods []string 200 | seen = make(map[string]bool) 201 | ) 202 | add := func(k string) { 203 | if seen[k] || boringKeys[k] || strings.HasPrefix(k, "_") { 204 | return 205 | } 206 | seen[k] = true 207 | 208 | key := SafeGet(obj, k) 209 | if key == nil { 210 | // The value corresponding to that key could not be found 211 | // (typically because it is backed by an RPC call that is 212 | // not supported by this instance. Add it to the list of 213 | // values so that it appears as `undefined` to the user. 214 | vals = append(vals, k) 215 | } else { 216 | if _, callable := goja.AssertFunction(key); callable { 217 | methods = append(methods, k) 218 | } else { 219 | vals = append(vals, k) 220 | } 221 | } 222 | 223 | } 224 | iterOwnAndConstructorKeys(ctx.vm, obj, add) 225 | sort.Strings(vals) 226 | sort.Strings(methods) 227 | return append(vals, methods...) 228 | } 229 | 230 | func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 231 | seen := make(map[string]bool) 232 | iterOwnKeys(vm, obj, func(prop string) { 233 | seen[prop] = true 234 | f(prop) 235 | }) 236 | if cp := constructorPrototype(vm, obj); cp != nil { 237 | iterOwnKeys(vm, cp, func(prop string) { 238 | if !seen[prop] { 239 | f(prop) 240 | } 241 | }) 242 | } 243 | } 244 | 245 | func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) { 246 | Object := vm.Get("Object").ToObject(vm) 247 | getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames")) 248 | if !isFunc { 249 | panic(vm.ToValue("Object.getOwnPropertyNames isn't a function")) 250 | } 251 | rv, err := getOwnPropertyNames(goja.Null(), obj) 252 | if err != nil { 253 | panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err))) 254 | } 255 | gv := rv.Export() 256 | switch gv := gv.(type) { 257 | case []interface{}: 258 | for _, v := range gv { 259 | f(v.(string)) 260 | } 261 | case []string: 262 | for _, v := range gv { 263 | f(v) 264 | } 265 | default: 266 | panic(fmt.Errorf("Object.getOwnPropertyNames returned unexpected type %T", gv)) 267 | } 268 | } 269 | 270 | func (ctx ppctx) isBigNumber(v *goja.Object) bool { 271 | // Handle numbers with custom constructor. 272 | if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil { 273 | if strings.HasPrefix(toString(obj), "function BigNumber") { 274 | return true 275 | } 276 | } 277 | // Handle default constructor. 278 | BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm) 279 | if BigNumber == nil { 280 | return false 281 | } 282 | prototype := BigNumber.Get("prototype").ToObject(ctx.vm) 283 | isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf")) 284 | if !callable { 285 | return false 286 | } 287 | bv, _ := isPrototypeOf(prototype, v) 288 | return bv.ToBoolean() 289 | } 290 | 291 | func toString(obj *goja.Object) string { 292 | return obj.ToString().String() 293 | } 294 | 295 | func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object { 296 | if v := obj.Get("constructor"); v != nil { 297 | if v := v.ToObject(vm).Get("prototype"); v != nil { 298 | return v.ToObject(vm) 299 | } 300 | } 301 | return nil 302 | } 303 | -------------------------------------------------------------------------------- /pkg/transaction/ethcontroller.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "time" 7 | 8 | "github.com/ethereum/go-ethereum/common/hexutil" 9 | "github.com/ethereum/go-ethereum/rlp" 10 | "github.com/harmony-one/go-sdk/pkg/address" 11 | "github.com/harmony-one/go-sdk/pkg/common" 12 | "github.com/harmony-one/go-sdk/pkg/rpc" 13 | "github.com/harmony-one/harmony/accounts" 14 | "github.com/harmony-one/harmony/accounts/keystore" 15 | "github.com/harmony-one/harmony/core/types" 16 | "github.com/harmony-one/harmony/numeric" 17 | ) 18 | 19 | type ethTransactionForRPC struct { 20 | params map[string]interface{} 21 | transaction *types.EthTransaction 22 | // Hex encoded 23 | signature *string 24 | transactionHash *string 25 | receipt rpc.Reply 26 | } 27 | 28 | // EthController drives the eth transaction signing process 29 | type EthController struct { 30 | executionError error 31 | transactionErrors Errors 32 | messenger rpc.T 33 | sender sender 34 | transactionForRPC ethTransactionForRPC 35 | chain common.ChainID 36 | Behavior behavior 37 | } 38 | 39 | // NewEthController initializes a EthController, caller can control behavior via options 40 | func NewEthController( 41 | handler rpc.T, senderKs *keystore.KeyStore, 42 | senderAcct *accounts.Account, chain common.ChainID, 43 | options ...func(*EthController), 44 | ) *EthController { 45 | txParams := make(map[string]interface{}) 46 | ctrlr := &EthController{ 47 | executionError: nil, 48 | messenger: handler, 49 | sender: sender{ 50 | ks: senderKs, 51 | account: senderAcct, 52 | }, 53 | transactionForRPC: ethTransactionForRPC{ 54 | params: txParams, 55 | signature: nil, 56 | transactionHash: nil, 57 | receipt: nil, 58 | }, 59 | chain: chain, 60 | Behavior: behavior{false, false, Software, 0}, 61 | } 62 | for _, option := range options { 63 | option(ctrlr) 64 | } 65 | return ctrlr 66 | } 67 | 68 | // EthTransactionToJSON dumps JSON representation 69 | func (C *EthController) EthTransactionToJSON(pretty bool) string { 70 | r, _ := C.transactionForRPC.transaction.MarshalJSON() 71 | if pretty { 72 | return common.JSONPrettyFormat(string(r)) 73 | } 74 | return string(r) 75 | } 76 | 77 | // RawTransaction dumps the signature as string 78 | func (C *EthController) RawTransaction() string { 79 | return *C.transactionForRPC.signature 80 | } 81 | 82 | // TransactionHash - the tx hash 83 | func (C *EthController) TransactionHash() *string { 84 | return C.transactionForRPC.transactionHash 85 | } 86 | 87 | // Receipt - the tx receipt 88 | func (C *EthController) Receipt() rpc.Reply { 89 | return C.transactionForRPC.receipt 90 | } 91 | 92 | // TransactionErrors - tx errors 93 | func (C *EthController) TransactionErrors() Errors { 94 | return C.transactionErrors 95 | } 96 | 97 | func (C *EthController) setIntrinsicGas(gasLimit uint64) { 98 | if C.executionError != nil { 99 | return 100 | } 101 | C.transactionForRPC.params["gas-limit"] = gasLimit 102 | } 103 | 104 | func (C *EthController) setGasPrice(gasPrice numeric.Dec) { 105 | if C.executionError != nil { 106 | return 107 | } 108 | if gasPrice.Sign() == -1 { 109 | C.executionError = ErrBadTransactionParam 110 | errorMsg := fmt.Sprintf( 111 | "can't set negative gas price: %d", gasPrice, 112 | ) 113 | C.transactionErrors = append(C.transactionErrors, &Error{ 114 | ErrMessage: &errorMsg, 115 | TimestampOfRejection: time.Now().Unix(), 116 | }) 117 | return 118 | } 119 | C.transactionForRPC.params["gas-price"] = gasPrice.Mul(nanoAsDec) 120 | } 121 | 122 | func (C *EthController) setAmount(amount numeric.Dec) { 123 | if C.executionError != nil { 124 | return 125 | } 126 | if amount.Sign() == -1 { 127 | C.executionError = ErrBadTransactionParam 128 | errorMsg := fmt.Sprintf( 129 | "can't set negative amount: %d", amount, 130 | ) 131 | C.transactionErrors = append(C.transactionErrors, &Error{ 132 | ErrMessage: &errorMsg, 133 | TimestampOfRejection: time.Now().Unix(), 134 | }) 135 | return 136 | } 137 | 138 | gasAsDec := C.transactionForRPC.params["gas-price"].(numeric.Dec) 139 | gasAsDec = gasAsDec.Mul(numeric.NewDec(int64(C.transactionForRPC.params["gas-limit"].(uint64)))) 140 | amountInAtto := amount.Mul(oneAsDec) 141 | total := amountInAtto.Add(gasAsDec) 142 | 143 | if !C.Behavior.OfflineSign { 144 | balanceRPCReply, err := C.messenger.SendRPC( 145 | rpc.Method.GetBalance, 146 | p{address.ToBech32(C.sender.account.Address), "latest"}, 147 | ) 148 | if err != nil { 149 | C.executionError = err 150 | return 151 | } 152 | currentBalance, _ := balanceRPCReply["result"].(string) 153 | bal, _ := new(big.Int).SetString(currentBalance[2:], 16) 154 | balance := numeric.NewDecFromBigInt(bal) 155 | if total.GT(balance) { 156 | balanceInOne := balance.Quo(oneAsDec) 157 | C.executionError = ErrBadTransactionParam 158 | errorMsg := fmt.Sprintf( 159 | "insufficient balance of %s in shard %d for the requested transfer of %s", 160 | balanceInOne.String(), C.transactionForRPC.params["from-shard"].(uint32), amount.String(), 161 | ) 162 | C.transactionErrors = append(C.transactionErrors, &Error{ 163 | ErrMessage: &errorMsg, 164 | TimestampOfRejection: time.Now().Unix(), 165 | }) 166 | return 167 | } 168 | } 169 | C.transactionForRPC.params["transfer-amount"] = amountInAtto 170 | } 171 | 172 | func (C *EthController) setReceiver(receiver string) { 173 | C.transactionForRPC.params["receiver"] = address.Parse(receiver) 174 | } 175 | 176 | func (C *EthController) setNewTransactionWithDataAndGas(data []byte) { 177 | if C.executionError != nil { 178 | return 179 | } 180 | C.transactionForRPC.transaction = NewEthTransaction( 181 | C.transactionForRPC.params["nonce"].(uint64), 182 | C.transactionForRPC.params["gas-limit"].(uint64), 183 | C.transactionForRPC.params["receiver"].(address.T), 184 | C.transactionForRPC.params["transfer-amount"].(numeric.Dec), 185 | C.transactionForRPC.params["gas-price"].(numeric.Dec), 186 | data, 187 | ) 188 | } 189 | 190 | func (C *EthController) signAndPrepareTxEncodedForSending() { 191 | if C.executionError != nil { 192 | return 193 | } 194 | signedTransaction, err := 195 | C.sender.ks.SignEthTx(*C.sender.account, C.transactionForRPC.transaction, C.chain.Value) 196 | if err != nil { 197 | C.executionError = err 198 | return 199 | } 200 | C.transactionForRPC.transaction = signedTransaction 201 | enc, _ := rlp.EncodeToBytes(signedTransaction) 202 | hexSignature := hexutil.Encode(enc) 203 | C.transactionForRPC.signature = &hexSignature 204 | if common.DebugTransaction { 205 | r, _ := signedTransaction.MarshalJSON() 206 | fmt.Println("Signed with ChainID:", C.transactionForRPC.transaction.ChainID()) 207 | fmt.Println(common.JSONPrettyFormat(string(r))) 208 | } 209 | } 210 | 211 | /*func (C *EthController) hardwareSignAndPrepareTxEncodedForSending() { 212 | if C.executionError != nil { 213 | return 214 | } 215 | enc, signerAddr, err := ledger.SignEthTx(C.transactionForRPC.transaction, C.chain.Value) 216 | if err != nil { 217 | C.executionError = err 218 | return 219 | } 220 | if strings.Compare(signerAddr, address.ToBech32(C.sender.account.Address)) != 0 { 221 | C.executionError = ErrBadTransactionParam 222 | errorMsg := "signature verification failed : sender address doesn't match with ledger hardware addresss" 223 | C.transactionErrors = append(C.transactionErrors, &Error{ 224 | ErrMessage: &errorMsg, 225 | TimestampOfRejection: time.Now().Unix(), 226 | }) 227 | return 228 | } 229 | hexSignature := hexutil.Encode(enc) 230 | C.transactionForRPC.signature = &hexSignature 231 | }*/ 232 | 233 | func (C *EthController) sendSignedTx() { 234 | if C.executionError != nil || C.Behavior.DryRun { 235 | return 236 | } 237 | reply, err := C.messenger.SendRPC(rpc.Method.SendRawTransaction, p{C.transactionForRPC.signature}) 238 | if err != nil { 239 | C.executionError = err 240 | return 241 | } 242 | r, _ := reply["result"].(string) 243 | C.transactionForRPC.transactionHash = &r 244 | } 245 | 246 | func (C *EthController) txConfirmation() { 247 | if C.executionError != nil || C.Behavior.DryRun { 248 | return 249 | } 250 | if C.Behavior.ConfirmationWaitTime > 0 { 251 | txHash := *C.TransactionHash() 252 | start := int(C.Behavior.ConfirmationWaitTime) 253 | for { 254 | r, _ := C.messenger.SendRPC(rpc.Method.GetTransactionReceipt, p{txHash}) 255 | if r["result"] != nil { 256 | C.transactionForRPC.receipt = r 257 | return 258 | } 259 | transactionErrors, err := GetError(txHash, C.messenger) 260 | if err != nil { 261 | errMsg := fmt.Sprintf(err.Error()) 262 | C.transactionErrors = append(C.transactionErrors, &Error{ 263 | TxHashID: &txHash, 264 | ErrMessage: &errMsg, 265 | TimestampOfRejection: time.Now().Unix(), 266 | }) 267 | } 268 | C.transactionErrors = append(C.transactionErrors, transactionErrors...) 269 | if len(transactionErrors) > 0 { 270 | C.executionError = fmt.Errorf("error found for transaction hash: %s", txHash) 271 | return 272 | } 273 | if start < 0 { 274 | C.executionError = fmt.Errorf("could not confirm transaction after %d seconds", C.Behavior.ConfirmationWaitTime) 275 | return 276 | } 277 | time.Sleep(time.Second) 278 | start-- 279 | } 280 | } 281 | } 282 | 283 | // ExecuteEthTransaction is the single entrypoint to execute an eth transaction. 284 | // Each step in transaction creation, execution probably includes a mutation 285 | // Each becomes a no-op if executionError occurred in any previous step 286 | func (C *EthController) ExecuteEthTransaction( 287 | nonce, gasLimit uint64, 288 | to string, 289 | amount, gasPrice numeric.Dec, 290 | inputData []byte, 291 | ) error { 292 | // WARNING Order of execution matters 293 | C.setIntrinsicGas(gasLimit) 294 | C.setGasPrice(gasPrice) 295 | C.setAmount(amount) 296 | C.setReceiver(to) 297 | C.transactionForRPC.params["nonce"] = nonce 298 | C.setNewTransactionWithDataAndGas(inputData) 299 | switch C.Behavior.SigningImpl { 300 | case Software: 301 | C.signAndPrepareTxEncodedForSending() 302 | /*case Ledger: 303 | C.hardwareSignAndPrepareTxEncodedForSending()*/ 304 | } 305 | C.sendSignedTx() 306 | C.txConfirmation() 307 | return C.executionError 308 | } 309 | 310 | func (C *EthController) ExecuteRawTransaction(txn string) error { 311 | C.transactionForRPC.signature = &txn 312 | 313 | C.sendSignedTx() 314 | C.txConfirmation() 315 | return C.executionError 316 | } 317 | -------------------------------------------------------------------------------- /pkg/console/jsre/jsre.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package jsre 18 | 19 | import ( 20 | crand "crypto/rand" 21 | "encoding/binary" 22 | "fmt" 23 | "io" 24 | "io/ioutil" 25 | "math/rand" 26 | "path/filepath" 27 | "time" 28 | 29 | "github.com/dop251/goja" 30 | ) 31 | 32 | // JSRE is a JS runtime environment embedding the goja interpreter. 33 | // It provides helper functions to load code from files, run code snippets 34 | // and bind native go objects to JS. 35 | // 36 | // The runtime runs all code on a dedicated event loop and does not expose the underlying 37 | // goja runtime directly. To use the runtime, call JSRE.Do. When binding a Go function, 38 | // use the Call type to gain access to the runtime. 39 | type JSRE struct { 40 | assetPath string 41 | output io.Writer 42 | evalQueue chan *evalReq 43 | stopEventLoop chan bool 44 | closed chan struct{} 45 | vm *goja.Runtime 46 | } 47 | 48 | // Call is the argument type of Go functions which are callable from JS. 49 | type Call struct { 50 | goja.FunctionCall 51 | VM *goja.Runtime 52 | } 53 | 54 | // jsTimer is a single timer instance with a callback function 55 | type jsTimer struct { 56 | timer *time.Timer 57 | duration time.Duration 58 | interval bool 59 | call goja.FunctionCall 60 | } 61 | 62 | // evalReq is a serialized vm execution request processed by runEventLoop. 63 | type evalReq struct { 64 | fn func(vm *goja.Runtime) 65 | done chan bool 66 | } 67 | 68 | // runtime must be stopped with Stop() after use and cannot be used after stopping 69 | func New(assetPath string, output io.Writer) *JSRE { 70 | re := &JSRE{ 71 | assetPath: assetPath, 72 | output: output, 73 | closed: make(chan struct{}), 74 | evalQueue: make(chan *evalReq), 75 | stopEventLoop: make(chan bool), 76 | vm: goja.New(), 77 | } 78 | go re.runEventLoop() 79 | re.Set("loadScript", MakeCallback(re.vm, re.loadScript)) 80 | re.Set("inspect", re.prettyPrintJS) 81 | return re 82 | } 83 | 84 | // randomSource returns a pseudo random value generator. 85 | func randomSource() *rand.Rand { 86 | bytes := make([]byte, 8) 87 | seed := time.Now().UnixNano() 88 | if _, err := crand.Read(bytes); err == nil { 89 | seed = int64(binary.LittleEndian.Uint64(bytes)) 90 | } 91 | 92 | src := rand.NewSource(seed) 93 | return rand.New(src) 94 | } 95 | 96 | // This function runs the main event loop from a goroutine that is started 97 | // when JSRE is created. Use Stop() before exiting to properly stop it. 98 | // The event loop processes vm access requests from the evalQueue in a 99 | // serialized way and calls timer callback functions at the appropriate time. 100 | 101 | // Exported functions always access the vm through the event queue. You can 102 | // call the functions of the goja vm directly to circumvent the queue. These 103 | // functions should be used if and only if running a routine that was already 104 | // called from JS through an RPC call. 105 | func (re *JSRE) runEventLoop() { 106 | defer close(re.closed) 107 | 108 | r := randomSource() 109 | re.vm.SetRandSource(r.Float64) 110 | 111 | registry := map[*jsTimer]*jsTimer{} 112 | ready := make(chan *jsTimer) 113 | 114 | newTimer := func(call goja.FunctionCall, interval bool) (*jsTimer, goja.Value) { 115 | delay := call.Argument(1).ToInteger() 116 | if 0 >= delay { 117 | delay = 1 118 | } 119 | timer := &jsTimer{ 120 | duration: time.Duration(delay) * time.Millisecond, 121 | call: call, 122 | interval: interval, 123 | } 124 | registry[timer] = timer 125 | 126 | timer.timer = time.AfterFunc(timer.duration, func() { 127 | ready <- timer 128 | }) 129 | 130 | return timer, re.vm.ToValue(timer) 131 | } 132 | 133 | setTimeout := func(call goja.FunctionCall) goja.Value { 134 | _, value := newTimer(call, false) 135 | return value 136 | } 137 | 138 | setInterval := func(call goja.FunctionCall) goja.Value { 139 | _, value := newTimer(call, true) 140 | return value 141 | } 142 | 143 | clearTimeout := func(call goja.FunctionCall) goja.Value { 144 | timer := call.Argument(0).Export() 145 | if timer, ok := timer.(*jsTimer); ok { 146 | timer.timer.Stop() 147 | delete(registry, timer) 148 | } 149 | return goja.Undefined() 150 | } 151 | re.vm.Set("_setTimeout", setTimeout) 152 | re.vm.Set("_setInterval", setInterval) 153 | re.vm.RunString(`var setTimeout = function(args) { 154 | if (arguments.length < 1) { 155 | throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present."); 156 | } 157 | return _setTimeout.apply(this, arguments); 158 | }`) 159 | re.vm.RunString(`var setInterval = function(args) { 160 | if (arguments.length < 1) { 161 | throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present."); 162 | } 163 | return _setInterval.apply(this, arguments); 164 | }`) 165 | re.vm.Set("clearTimeout", clearTimeout) 166 | re.vm.Set("clearInterval", clearTimeout) 167 | 168 | var waitForCallbacks bool 169 | 170 | loop: 171 | for { 172 | select { 173 | case timer := <-ready: 174 | // execute callback, remove/reschedule the timer 175 | var arguments []interface{} 176 | if len(timer.call.Arguments) > 2 { 177 | tmp := timer.call.Arguments[2:] 178 | arguments = make([]interface{}, 2+len(tmp)) 179 | for i, value := range tmp { 180 | arguments[i+2] = value 181 | } 182 | } else { 183 | arguments = make([]interface{}, 1) 184 | } 185 | arguments[0] = timer.call.Arguments[0] 186 | call, isFunc := goja.AssertFunction(timer.call.Arguments[0]) 187 | if !isFunc { 188 | panic(re.vm.ToValue("js error: timer/timeout callback is not a function")) 189 | } 190 | call(goja.Null(), timer.call.Arguments...) 191 | 192 | _, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it 193 | if timer.interval && inreg { 194 | timer.timer.Reset(timer.duration) 195 | } else { 196 | delete(registry, timer) 197 | if waitForCallbacks && (len(registry) == 0) { 198 | break loop 199 | } 200 | } 201 | case req := <-re.evalQueue: 202 | // run the code, send the result back 203 | req.fn(re.vm) 204 | close(req.done) 205 | if waitForCallbacks && (len(registry) == 0) { 206 | break loop 207 | } 208 | case waitForCallbacks = <-re.stopEventLoop: 209 | if !waitForCallbacks || (len(registry) == 0) { 210 | break loop 211 | } 212 | } 213 | } 214 | 215 | for _, timer := range registry { 216 | timer.timer.Stop() 217 | delete(registry, timer) 218 | } 219 | } 220 | 221 | // Do executes the given function on the JS event loop. 222 | func (re *JSRE) Do(fn func(*goja.Runtime)) { 223 | done := make(chan bool) 224 | req := &evalReq{fn, done} 225 | re.evalQueue <- req 226 | <-done 227 | } 228 | 229 | // stops the event loop before exit, optionally waits for all timers to expire 230 | func (re *JSRE) Stop(waitForCallbacks bool) { 231 | select { 232 | case <-re.closed: 233 | case re.stopEventLoop <- waitForCallbacks: 234 | <-re.closed 235 | } 236 | } 237 | 238 | // Exec(file) loads and runs the contents of a file 239 | // if a relative path is given, the jsre's assetPath is used 240 | func (re *JSRE) Exec(file string) error { 241 | code, err := ioutil.ReadFile(AbsolutePath(re.assetPath, file)) 242 | if err != nil { 243 | return err 244 | } 245 | return re.Compile(file, string(code)) 246 | } 247 | 248 | // Run runs a piece of JS code. 249 | func (re *JSRE) Run(code string) (v goja.Value, err error) { 250 | re.Do(func(vm *goja.Runtime) { v, err = vm.RunString(code) }) 251 | return v, err 252 | } 253 | 254 | // Set assigns value v to a variable in the JS environment. 255 | func (re *JSRE) Set(ns string, v interface{}) (err error) { 256 | re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) }) 257 | return err 258 | } 259 | 260 | // MakeCallback turns the given function into a function that's callable by JS. 261 | func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Value { 262 | return vm.ToValue(func(call goja.FunctionCall) goja.Value { 263 | result, err := fn(Call{call, vm}) 264 | if err != nil { 265 | panic(vm.NewGoError(err)) 266 | } 267 | return result 268 | }) 269 | } 270 | 271 | // Evaluate executes code and pretty prints the result to the specified output stream. 272 | func (re *JSRE) Evaluate(code string, w io.Writer) { 273 | re.Do(func(vm *goja.Runtime) { 274 | val, err := vm.RunString(code) 275 | if err != nil { 276 | prettyError(vm, err, w) 277 | } else { 278 | prettyPrint(vm, val, w) 279 | } 280 | fmt.Fprintln(w) 281 | }) 282 | } 283 | 284 | // Compile compiles and then runs a piece of JS code. 285 | func (re *JSRE) Compile(filename string, src string) (err error) { 286 | re.Do(func(vm *goja.Runtime) { _, err = compileAndRun(vm, filename, src) }) 287 | return err 288 | } 289 | 290 | // loadScript loads and executes a JS file. 291 | func (re *JSRE) loadScript(call Call) (goja.Value, error) { 292 | file := call.Argument(0).ToString().String() 293 | file = AbsolutePath(re.assetPath, file) 294 | source, err := ioutil.ReadFile(file) 295 | if err != nil { 296 | return nil, fmt.Errorf("Could not read file %s: %v", file, err) 297 | } 298 | value, err := compileAndRun(re.vm, file, string(source)) 299 | if err != nil { 300 | return nil, fmt.Errorf("Error while compiling or running script: %v", err) 301 | } 302 | return value, nil 303 | } 304 | 305 | func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, error) { 306 | script, err := goja.Compile(filename, src, false) 307 | if err != nil { 308 | return goja.Null(), err 309 | } 310 | return vm.RunProgram(script) 311 | } 312 | 313 | // AbsolutePath returns datadir + filename, or filename if it is absolute. 314 | func AbsolutePath(datadir string, filename string) string { 315 | if filepath.IsAbs(filename) { 316 | return filename 317 | } 318 | return filepath.Join(datadir, filename) 319 | } 320 | -------------------------------------------------------------------------------- /.github/workflows/hmybuild.yml: -------------------------------------------------------------------------------- 1 | name: release hmy 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | env: 9 | GOPATH: ${{ github.workspace }} 10 | GOBIN: ${{ github.workspace }}/bin 11 | 12 | jobs: 13 | build-x86_64: 14 | name: Build hmy binary for x86_64 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ ubuntu-22.04, macos-latest ] 19 | 20 | steps: 21 | - name: Checkout hmy code 22 | uses: actions/checkout@v4 23 | with: 24 | path: go-sdk 25 | 26 | - name: Set up Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version-file: go-sdk/go.mod 30 | 31 | - name: Checkout dependence repo 32 | uses: actions/checkout@v4 33 | with: 34 | repository: harmony-one/mcl 35 | path: ${{ github.workspace }}/src/github.com/harmony-one/mcl 36 | ref: master 37 | fetch-depth: 0 38 | 39 | - name: Checkout dependence repo 40 | uses: actions/checkout@v4 41 | with: 42 | repository: harmony-one/bls 43 | path: ${{ github.workspace }}/src/github.com/harmony-one/bls 44 | ref: master 45 | fetch-depth: 0 46 | 47 | - name: Checkout dependence code 48 | uses: actions/checkout@v4 49 | with: 50 | repository: harmony-one/harmony 51 | path: ${{ github.workspace }}/src/github.com/harmony-one/harmony 52 | ref: main 53 | fetch-depth: 0 54 | 55 | - name: Get latest version and release 56 | run: | 57 | VERSION=$(git tag -l --sort=-v:refname | head -n 1 | tr -d v) 58 | RELEASE=$(git describe --long | cut -f2 -d-) 59 | echo "build_version=$VERSION" >> $GITHUB_ENV 60 | echo "build_release=$RELEASE" >> $GITHUB_ENV 61 | working-directory: go-sdk 62 | 63 | - name: Debug 64 | run: | 65 | pwd 66 | echo ${HOME} 67 | echo ${GITHUB_WORKSPACE} 68 | echo ${GOPATH} 69 | echo ${GOROOT} 70 | 71 | - name: Build hmy binary for Linux 72 | if: matrix.os == 'ubuntu-22.04' 73 | run: | 74 | make static 75 | working-directory: go-sdk 76 | 77 | - name: Build libs for macos-latest 78 | if: matrix.os == 'macos-latest' 79 | run: | 80 | brew install gmp 81 | brew install openssl 82 | sudo mkdir -p /opt/homebrew/opt/ 83 | sudo ln -sf /usr/local/opt/openssl@1.1 /opt/homebrew/opt/openssl@1.1 84 | echo "ls -l /opt/homebrew/opt/openssl@1.1"; ls -l /opt/homebrew/opt/openssl@1.1 85 | make libs 86 | working-directory: ${{ github.workspace }}/src/github.com/harmony-one/harmony 87 | 88 | - name: Build hmy binary for macos-latest x86_64 89 | if: matrix.os == 'macos-latest' 90 | run: | 91 | make all 92 | mv dist/hmy dist/hmy-darwin-x86_64 93 | working-directory: go-sdk 94 | 95 | - name: Upload artifact 96 | if: matrix.os != 'macos-latest' 97 | uses: actions/upload-artifact@v4 98 | with: 99 | name: hmy 100 | path: ${{ github.workspace }}/go-sdk/dist/* 101 | retention-days: 1 102 | 103 | - name: Upload artifact darwin 104 | if: matrix.os == 'macos-latest' 105 | uses: actions/upload-artifact@v4 106 | with: 107 | name: hmy-darwin-x86_64 108 | path: ${{ github.workspace }}/go-sdk/dist/hmy-darwin-x86_64 109 | retention-days: 1 110 | 111 | # build-arm64: 112 | # name: Build hmy binary 113 | # runs-on: ${{ matrix.os }} 114 | # strategy: 115 | # matrix: 116 | # os: [ [ self-hosted,linux,ARM64 ] ] 117 | 118 | # steps: 119 | 120 | # - name: Set up Go 1.16.5 121 | # uses: actions/setup-go@v2 122 | # with: 123 | # go-version: 1.16.5 124 | 125 | 126 | # - name: Checkout hmy code 127 | # uses: actions/checkout@v2 128 | # with: 129 | # path: go/src/github.com/harmony-one/go-sdk 130 | 131 | # - name: Debug 132 | # run: | 133 | # pwd 134 | # echo ${HOME} 135 | # echo ${GITHUB_WORKSPACE} 136 | # echo ${GOPATH} 137 | # echo ${GOROOT} 138 | # env: 139 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 140 | 141 | # - name: Checkout dependence repo 142 | # uses: actions/checkout@v2 143 | # with: 144 | # repository: harmony-one/mcl 145 | # path: go/src/github.com/harmony-one/mcl 146 | # env: 147 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 148 | 149 | # - name: Checkout dependence repo 150 | # uses: actions/checkout@v2 151 | # with: 152 | # repository: harmony-one/bls 153 | # path: go/src/github.com/harmony-one/bls 154 | # env: 155 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 156 | 157 | # - name: Checkout dependence code 158 | # uses: actions/checkout@v2 159 | # with: 160 | # repository: harmony-one/harmony 161 | # path: go/src/github.com/harmony-one/harmony 162 | # ref: main 163 | # fetch-depth: 0 164 | # env: 165 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 166 | 167 | # - name: Build hmy binary for Arm 168 | # run: | 169 | # make static 170 | # mv dist/hmy dist/hmy-arm64 171 | # chmod +x dist/hmy-arm64 172 | # working-directory: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go/src/github.com/harmony-one/go-sdk 173 | # env: 174 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 175 | 176 | # - name: Upload artifact 177 | # uses: actions/upload-artifact@v4 178 | # with: 179 | # name: hmy-arm64 180 | # path: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go/src/github.com/harmony-one/go-sdk/dist/* 181 | # retention-days: 1 182 | # env: 183 | # GOPATH: /home/ubuntu/actions-runner/_work/go-sdk/go-sdk/go 184 | 185 | release-page: 186 | name: Sign binary and create and publish release page 187 | needs: [ build-x86_64 ] 188 | runs-on: ubuntu-22.04 189 | steps: 190 | - name: Import GPG key 191 | uses: crazy-max/ghaction-import-gpg@v6 192 | with: 193 | gpg_private_key: ${{ secrets.HMY_GPG_PRIVATE_KEY }} 194 | passphrase: ${{ secrets.HMY_GPG_PRIVATE_KEY_PASS }} 195 | 196 | - name: Checkout hmy core code 197 | uses: actions/checkout@v4 198 | with: 199 | path: go-sdk 200 | 201 | - name: Get latest version 202 | run: | 203 | VERSION=$(git tag -l --sort=-v:refname | head -n 1 | tr -d v) 204 | VERSION_LONG=$(git describe --always --long --dirty) 205 | RELEASE=$(git describe --long | cut -f2 -d-) 206 | echo "build_version=$VERSION" >> $GITHUB_ENV 207 | echo "build_version_long=$VERSION_LONG" >> $GITHUB_ENV 208 | echo "build_release=$RELEASE" >> $GITHUB_ENV 209 | working-directory: go-sdk 210 | 211 | - name: Download artifact 212 | uses: actions/download-artifact@v4 213 | with: 214 | name: hmy 215 | 216 | - name: Download artifact 217 | uses: actions/download-artifact@v4 218 | with: 219 | name: hmy-darwin-x86_64 220 | 221 | - name: Display structure of downloaded files 222 | run: ls -R 223 | 224 | - name: Signed darwin x86_64 hmy binary 225 | run: | 226 | gpg --detach-sign hmy-darwin-x86_64 227 | sha256sum hmy-darwin-x86_64 >> hmy-darwin-x86_64.sha256 228 | 229 | - name: Get tag message 230 | env: 231 | TAG_SHA: ${{ github.event.after }} 232 | run: | 233 | touch ./tag_message.md 234 | echo -e "$TAG_SHA\n\nThe released version: $build_version_long" >> ./tag_message.md 235 | working-directory: go-sdk 236 | 237 | - name: Create Release 238 | id: create_release 239 | uses: actions/create-release@v1 240 | env: 241 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 242 | with: 243 | tag_name: ${{ github.ref }} 244 | release_name: Mainnet Release ${{ env.build_version }} 245 | draft: true 246 | prerelease: false 247 | body_path: ${{ github.workspace }}/go-sdk/tag_message.md 248 | 249 | - name: Upload hmy binary for Linux (x86_64) 250 | uses: actions/upload-release-asset@v1 251 | env: 252 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 253 | with: 254 | upload_url: ${{ steps.create_release.outputs.upload_url }} 255 | asset_path: ./hmy 256 | asset_name: hmy 257 | asset_content_type: application/octet-stream 258 | 259 | - name: Upload hmy binary darwin-x86_64 260 | uses: actions/upload-release-asset@v1 261 | env: 262 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 263 | with: 264 | upload_url: ${{ steps.create_release.outputs.upload_url }} 265 | asset_path: ./hmy-darwin-x86_64 266 | asset_name: hmy-darwin-x86_64 267 | asset_content_type: application/octet-stream 268 | 269 | 270 | # - name: Upload hmy binary for ARM64 271 | # uses: actions/upload-release-asset@v1 272 | # env: 273 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 274 | # GOPATH: /home/runner/work/go-sdk/go-sdk/go 275 | # with: 276 | # upload_url: ${{ steps.create_release.outputs.upload_url }} 277 | # asset_path: ./hmy-arm64 278 | # asset_name: hmy-arm64 279 | # asset_content_type: application/octet-stream 280 | 281 | # - name: Upload sha256 signature of hmy arm64 binary 282 | # uses: actions/upload-release-asset@v1 283 | # env: 284 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 285 | # with: 286 | # upload_url: ${{ steps.create_release.outputs.upload_url }} 287 | # asset_path: ./hmy-arm64.sha256 288 | # asset_name: hmy-arm64.sha256 289 | # asset_content_type: text/plain 290 | 291 | # - name: Upload gpg signature of hmy arm64 binary 292 | # uses: actions/upload-release-asset@v1 293 | # env: 294 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 295 | # with: 296 | # upload_url: ${{ steps.create_release.outputs.upload_url }} 297 | # asset_path: ./hmy-arm64.sig 298 | # asset_name: hmy-arm64.sig 299 | # asset_content_type: application/octet-stream 300 | --------------------------------------------------------------------------------