// Copyright 2015, 2016 Eris Industries (UK) Ltd. // This file is part of Eris-RT // Eris-RT is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Eris-RT is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Eris-RT. If not, see <http://www.gnu.org/licenses/>. package core import ( "bytes" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" // "strings" // "time" "github.com/tendermint/go-crypto" "github.com/tendermint/go-rpc/client" // ptypes "github.com/eris-ltd/permission/types" log "github.com/eris-ltd/eris-logger" "github.com/eris-ltd/eris-db/account" tendermint_client "github.com/eris-ltd/eris-db/rpc/tendermint/client" "github.com/eris-ltd/eris-db/txs" ) var ( MaxCommitWaitTimeSeconds = 20 ) //------------------------------------------------------------------------------------ // core functions with string args. // validates strings and forms transaction func Send(nodeAddr, signAddr, pubkey, addr, toAddr, amtS, nonceS string) (*txs.SendTx, error) { pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, addr, amtS, nonceS) if err != nil { return nil, err } if toAddr == "" { return nil, fmt.Errorf("destination address must be given with --to flag") } toAddrBytes, err := hex.DecodeString(toAddr) if err != nil { return nil, fmt.Errorf("toAddr is bad hex: %v", err) } tx := txs.NewSendTx() tx.AddInputWithNonce(pub, amt, int(nonce)) tx.AddOutput(toAddrBytes, amt) return tx, nil } // func Call(nodeAddr, signAddr, pubkey, addr, toAddr, amtS, nonceS, gasS, feeS, data string) (*txs.CallTx, error) { // pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, addr, amtS, nonceS) // if err != nil { // return nil, err // } // toAddrBytes, err := hex.DecodeString(toAddr) // if err != nil { // return nil, fmt.Errorf("toAddr is bad hex: %v", err) // } // fee, err := strconv.ParseInt(feeS, 10, 64) // if err != nil { // return nil, fmt.Errorf("fee is misformatted: %v", err) // } // gas, err := strconv.ParseInt(gasS, 10, 64) // if err != nil { // return nil, fmt.Errorf("gas is misformatted: %v", err) // } // dataBytes, err := hex.DecodeString(data) // if err != nil { // return nil, fmt.Errorf("data is bad hex: %v", err) // } // tx := types.NewCallTxWithNonce(pub, toAddrBytes, dataBytes, amt, gas, fee, int(nonce)) // return tx, nil // } // func Name(nodeAddr, signAddr, pubkey, addr, amtS, nonceS, feeS, name, data string) (*txs.NameTx, error) { // pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, addr, amtS, nonceS) // if err != nil { // return nil, err // } // fee, err := strconv.ParseInt(feeS, 10, 64) // if err != nil { // return nil, fmt.Errorf("fee is misformatted: %v", err) // } // tx := types.NewNameTxWithNonce(pub, name, data, amt, fee, int(nonce)) // return tx, nil // } // type PermFunc struct { // Name string // Args string // } // var PermsFuncs = []PermFunc{ // {"set_base", "address, permission flag, value"}, // {"unset_base", "address, permission flag"}, // {"set_global", "permission flag, value"}, // {"add_role", "address, role"}, // {"rm_role", "address, role"}, // } // func Permissions(nodeAddr, signAddr, pubkey, addrS, nonceS, permFunc string, argsS []string) (*txs.PermissionsTx, error) { // pub, _, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, addrS, "0", nonceS) // if err != nil { // return nil, err // } // var args ptypes.PermArgs // switch permFunc { // case "set_base": // addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) // if err != nil { // return nil, err // } // if len(argsS) != 3 { // return nil, fmt.Errorf("set_base also takes a value (true or false)") // } // var value bool // if argsS[2] == "true" { // value = true // } else if argsS[2] == "false" { // value = false // } else { // return nil, fmt.Errorf("Unknown value %s", argsS[2]) // } // args = &ptypes.SetBaseArgs{addr, pF, value} // case "unset_base": // addr, pF, err := decodeAddressPermFlag(argsS[0], argsS[1]) // if err != nil { // return nil, err // } // args = &ptypes.UnsetBaseArgs{addr, pF} // case "set_global": // pF, err := ptypes.PermStringToFlag(argsS[0]) // if err != nil { // return nil, err // } // var value bool // if argsS[1] == "true" { // value = true // } else if argsS[1] == "false" { // value = false // } else { // return nil, fmt.Errorf("Unknown value %s", argsS[1]) // } // args = &ptypes.SetGlobalArgs{pF, value} // case "add_role": // addr, err := hex.DecodeString(argsS[0]) // if err != nil { // return nil, err // } // args = &ptypes.AddRoleArgs{addr, argsS[1]} // case "rm_role": // addr, err := hex.DecodeString(argsS[0]) // if err != nil { // return nil, err // } // args = &ptypes.RmRoleArgs{addr, argsS[1]} // default: // return nil, fmt.Errorf("Invalid permission function for use in PermissionsTx: %s", permFunc) // } // // args := snativeArgs( // tx := types.NewPermissionsTxWithNonce(pub, args, int(nonce)) // return tx, nil // } // func decodeAddressPermFlag(addrS, permFlagS string) (addr []byte, pFlag ptypes.PermFlag, err error) { // if addr, err = hex.DecodeString(addrS); err != nil { // return // } // if pFlag, err = ptypes.PermStringToFlag(permFlagS); err != nil { // return // } // return // } // type NameGetter struct { // client cclient.Client // } // func (n NameGetter) GetNameRegEntry(name string) *txs.NameRegEntry { // entry, err := n.client.GetName(name) // if err != nil { // panic(err) // } // return entry.Entry // } /* func coreNewAccount(nodeAddr, pubkey, chainID string) (*types.NewAccountTx, error) { pub, _, _, err := checkCommon(nodeAddr, pubkey, "", "0", "0") if err != nil { return nil, err } client := cclient.NewClient(nodeAddr, "HTTP") return types.NewNewAccountTx(NameGetter{client}, pub, chainID) } */ // func Bond(nodeAddr, signAddr, pubkey, unbondAddr, amtS, nonceS string) (*txs.BondTx, error) { // pub, amt, nonce, err := checkCommon(nodeAddr, signAddr, pubkey, "", amtS, nonceS) // if err != nil { // return nil, err // } // var pubKey crypto.PubKeyEd25519 // var unbondAddrBytes []byte // if unbondAddr == "" { // pkb, _ := hex.DecodeString(pubkey) // copy(pubKey[:], pkb) // unbondAddrBytes = pubKey.Address() // } else { // unbondAddrBytes, err = hex.DecodeString(unbondAddr) // if err != nil { // return nil, fmt.Errorf("unbondAddr is bad hex: %v", err) // } // } // tx, err := types.NewBondTx(pub) // if err != nil { // return nil, err // } // tx.AddInputWithNonce(pub, amt, int(nonce)) // tx.AddOutput(unbondAddrBytes, amt) // return tx, nil // } // func Unbond(addrS, heightS string) (*txs.UnbondTx, error) { // if addrS == "" { // return nil, fmt.Errorf("Validator address must be given with --addr flag") // } // addrBytes, err := hex.DecodeString(addrS) // if err != nil { // return nil, fmt.Errorf("addr is bad hex: %v", err) // } // height, err := strconv.ParseInt(heightS, 10, 32) // if err != nil { // return nil, fmt.Errorf("height is misformatted: %v", err) // } // return &types.UnbondTx{ // Address: addrBytes, // Height: int(height), // }, nil // } // func Rebond(addrS, heightS string) (*txs.RebondTx, error) { // if addrS == "" { // return nil, fmt.Errorf("Validator address must be given with --addr flag") // } // addrBytes, err := hex.DecodeString(addrS) // if err != nil { // return nil, fmt.Errorf("addr is bad hex: %v", err) // } // height, err := strconv.ParseInt(heightS, 10, 32) // if err != nil { // return nil, fmt.Errorf("height is misformatted: %v", err) // } // return &types.RebondTx{ // Address: addrBytes, // Height: int(height), // }, nil // } //------------------------------------------------------------------------------------ // sign and broadcast func Pub(addr, rpcAddr string) (pubBytes []byte, err error) { args := map[string]string{ "addr": addr, } pubS, err := RequestResponse(rpcAddr, "pub", args) if err != nil { return } return hex.DecodeString(pubS) } func Sign(signBytes, signAddr, signRPC string) (sig [64]byte, err error) { args := map[string]string{ "msg": signBytes, "hash": signBytes, // TODO:[ben] backwards compatibility "addr": signAddr, } sigS, err := RequestResponse(signRPC, "sign", args) if err != nil { return } sigBytes, err := hex.DecodeString(sigS) if err != nil { return } copy(sig[:], sigBytes) return } func Broadcast(tx txs.Tx, broadcastRPC string) (*txs.Receipt, error) { client := rpcclient.NewClientURI(broadcastRPC) receipt, err := tendermint_client.BroadcastTx(client, tx) if err != nil { return nil, err } return &receipt, nil } //------------------------------------------------------------------------------------ // utils for talking to the key server type HTTPResponse struct { Response string Error string } func RequestResponse(addr, method string, args map[string]string) (string, error) { b, err := json.Marshal(args) if err != nil { return "", err } endpoint := fmt.Sprintf("%s/%s", addr, method) log.WithFields(log.Fields{ "key server endpoint": endpoint, "request body": string(b), }).Debugf("Sending request body to key server") req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(b)) if err != nil { return "", err } req.Header.Add("Content-Type", "application/json") res, errS, err := requestResponse(req) if err != nil { return "", fmt.Errorf("Error calling eris-keys at %s: %s", endpoint, err.Error()) } if errS != "" { return "", fmt.Errorf("Error (string) calling eris-keys at %s: %s", endpoint, errS) } log.WithFields(log.Fields{ "endpoint": endpoint, "request body": string(b), "response": res, }).Debugf("Received response from key server") return res, nil } func requestResponse(req *http.Request) (string, string, error) { client := new(http.Client) resp, err := client.Do(req) if err != nil { return "", "", err } if resp.StatusCode >= 400 { return "", "", fmt.Errorf(resp.Status) } return unpackResponse(resp) } func unpackResponse(resp *http.Response) (string, string, error) { b, err := ioutil.ReadAll(resp.Body) if err != nil { return "", "", err } r := new(HTTPResponse) if err := json.Unmarshal(b, r); err != nil { return "", "", err } return r.Response, r.Error, nil } //------------------------------------------------------------------------------------ // sign and broadcast convenience // tx has either one input or we default to the first one (ie for send/bond) // TODO: better support for multisig and bonding func signTx(signAddr, chainID string, tx_ txs.Tx) ([]byte, txs.Tx, error) { signBytes := fmt.Sprintf("%X", account.SignBytes(chainID, tx_)) var inputAddr []byte var sigED crypto.SignatureEd25519 switch tx := tx_.(type) { case *txs.SendTx: inputAddr = tx.Inputs[0].Address defer func(s *crypto.SignatureEd25519) { tx.Inputs[0].Signature = *s }(&sigED) case *txs.NameTx: inputAddr = tx.Input.Address defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) case *txs.CallTx: inputAddr = tx.Input.Address defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) case *txs.PermissionsTx: inputAddr = tx.Input.Address defer func(s *crypto.SignatureEd25519) { tx.Input.Signature = *s }(&sigED) case *txs.BondTx: inputAddr = tx.Inputs[0].Address defer func(s *crypto.SignatureEd25519) { tx.Signature = *s tx.Inputs[0].Signature = *s }(&sigED) case *txs.UnbondTx: inputAddr = tx.Address defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) case *txs.RebondTx: inputAddr = tx.Address defer func(s *crypto.SignatureEd25519) { tx.Signature = *s }(&sigED) } addrHex := fmt.Sprintf("%X", inputAddr) sig, err := Sign(signBytes, addrHex, signAddr) if err != nil { return nil, nil, err } sigED = crypto.SignatureEd25519(sig) log.WithFields(log.Fields{ "transaction sign bytes": signBytes, "account address": addrHex, "signature": fmt.Sprintf("%X", sig), }).Debug("Signed transaction") return inputAddr, tx_, nil } type TxResult struct { BlockHash []byte // all txs get in a block Hash []byte // all txs get a hash // only CallTx Address []byte // only for new contracts Return []byte Exception string //TODO: make Broadcast() errors more responsive so we // can differentiate mempool errors from other } func SignAndBroadcast(chainID, nodeAddr, signAddr string, tx txs.Tx, sign, broadcast, wait bool) (txResult *TxResult, err error) { // var inputAddr []byte if sign { _, tx, err = signTx(signAddr, chainID, tx) if err != nil { return nil, err } } if broadcast { // if wait { // var ch chan Msg // ch, err = subscribeAndWait(tx, chainID, nodeAddr, inputAddr) // if err != nil { // return nil, err // } else { // defer func() { // if err != nil { // // if broadcast threw an error, just return // return // } // log.WithFields(log.Fields{ // "", // }).Debug("Waiting for tx to be committed") // msg := <-ch // if msg.Error != nil { // logger.Infof("Encountered error waiting for event: %v\n", msg.Error) // err = msg.Error // } else { // txResult.BlockHash = msg.BlockHash // txResult.Return = msg.Value // txResult.Exception = msg.Exception // } // }() // } // } var receipt *txs.Receipt receipt, err = Broadcast(tx, nodeAddr) if err != nil { return nil, err } txResult = &TxResult{ Hash: receipt.TxHash, } // NOTE: [ben] is this consistent with the Ethereum protocol? It should seem // reasonable to get this returned from the chain directly. Alternatively, // the benefit is that the we don't need to trust the chain node if tx_, ok := tx.(*txs.CallTx); ok { if len(tx_.Address) == 0 { txResult.Address = txs.NewContractAddress(tx_.Input.Address, tx_.Input.Sequence) } } } return } //------------------------------------------------------------------------------------ // wait for events type Msg struct { BlockHash []byte Value []byte Exception string Error error } // func subscribeAndWait(tx txs.Tx, chainID, nodeAddr string, inputAddr []byte) (chan Msg, error) { // // subscribe to event and wait for tx to be committed // var wsAddr string // if strings.HasPrefix(nodeAddr, "http://") { // wsAddr = strings.TrimPrefix(nodeAddr, "http://") // } // if strings.HasPrefix(nodeAddr, "tcp://") { // wsAddr = strings.TrimPrefix(nodeAddr, "tcp://") // } // if strings.HasPrefix(nodeAddr, "unix://") { // log.WithFields(log.Fields{ // "node address": nodeAddr, // }).Warn("Unable to subscribe to websocket from unix socket.") // return nil, fmt.Errorf("Unable to subscribe to websocket from unix socket: %s", nodeAddr) // } // wsAddr = "ws://" + wsAddr // log.WithFields(log.Fields{ // "websocket address": wsAddr, // "endpoint": "/websocket", // }).Debug("Subscribing to websocket address") // wsClient := rpcclient.NewWSClient(wsAddr, "/websocket") // wsClient.Start() // eid := txs.EventStringAccInput(inputAddr) // if err := wsClient.Subscribe(eid); err != nil { // return nil, fmt.Errorf("Error subscribing to AccInput event: %v", err) // } // if err := wsClient.Subscribe(txs.EventStringNewBlock()); err != nil { // return nil, fmt.Errorf("Error subscribing to NewBlock event: %v", err) // } // resultChan := make(chan Msg, 1) // var latestBlockHash []byte // // Read message // go func() { // for { // result := <-wsClient.EventsCh // // if its a block, remember the block hash // blockData, ok := result.Data.(txs.EventDataNewBlock) // if ok { // log.Infoln(blockData.Block) // latestBlockHash = blockData.Block.Hash() // continue // } // // we don't accept events unless they came after a new block (ie. in) // if latestBlockHash == nil { // continue // } // if result.Event != eid { // logger.Debugf("received unsolicited event! Got %s, expected %s\n", result.Event, eid) // continue // } // data, ok := result.Data.(types.EventDataTx) // if !ok { // resultChan <- Msg{Error: fmt.Errorf("response error: expected result.Data to be *types.EventDataTx")} // return // } // if !bytes.Equal(types.TxID(chainID, data.Tx), types.TxID(chainID, tx)) { // logger.Debugf("Received event for same input from another transaction: %X\n", types.TxID(chainID, data.Tx)) // continue // } // if data.Exception != "" { // resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return, Exception: data.Exception} // return // } // // GOOD! // resultChan <- Msg{BlockHash: latestBlockHash, Value: data.Return} // return // } // }() // // txs should take no more than 10 seconds // timeoutTicker := time.Tick(time.Duration(MaxCommitWaitTimeSeconds) * time.Second) // go func() { // <-timeoutTicker // resultChan <- Msg{Error: fmt.Errorf("timed out waiting for event")} // return // }() // return resultChan, nil // } //------------------------------------------------------------------------------------ // convenience function func checkCommon(nodeAddr, signAddr, pubkey, addr, amtS, nonceS string) (pub crypto.PubKey, amt int64, nonce int64, err error) { if amtS == "" { err = fmt.Errorf("input must specify an amount with the --amt flag") return } var pubKeyBytes []byte if pubkey == "" && addr == "" { err = fmt.Errorf("at least one of --pubkey or --addr must be given") return } else if pubkey != "" { if addr != "" { log.WithFields(log.Fields{ "public key": pubkey, "address": addr, }).Info("you have specified both a pubkey and an address. the pubkey takes precedent") } pubKeyBytes, err = hex.DecodeString(pubkey) if err != nil { err = fmt.Errorf("pubkey is bad hex: %v", err) return } } else { // grab the pubkey from eris-keys pubKeyBytes, err = Pub(addr, signAddr) if err != nil { err = fmt.Errorf("failed to fetch pubkey for address (%s): %v", addr, err) return } } if len(pubKeyBytes) == 0 { err = fmt.Errorf("Error resolving public key") return } amt, err = strconv.ParseInt(amtS, 10, 64) if err != nil { err = fmt.Errorf("amt is misformatted: %v", err) } var pubArray [32]byte copy(pubArray[:], pubKeyBytes) pub = crypto.PubKeyEd25519(pubArray) addrBytes := pub.Address() if nonceS == "" { if nodeAddr == "" { err = fmt.Errorf("input must specify a nonce with the --nonce flag or use --node-addr (or ERIS_CLIENT_NODE_ADDR) to fetch the nonce from a node") return } // fetch nonce from node client := rpcclient.NewClientURI(nodeAddr) account, err2 := tendermint_client.GetAccount(client, addrBytes) if err2 != nil { err = fmt.Errorf("Error connecting to node (%s) to fetch nonce: %s", nodeAddr, err2.Error()) return } if account == nil { err = fmt.Errorf("unknown account %X", addrBytes) return } nonce = int64(account.Sequence) + 1 log.WithFields(log.Fields{ "nonce": nonce, "node address": nodeAddr, "account address": fmt.Sprintf("%X", addrBytes), }).Debug("Fetch nonce from node") } else { nonce, err = strconv.ParseInt(nonceS, 10, 64) if err != nil { err = fmt.Errorf("nonce is misformatted: %v", err) return } } return }