2015-03-22 19:00:08 -07:00
package types
2014-06-05 02:34:45 -07:00
import (
2015-06-01 14:59:10 -07:00
"encoding/json"
2014-12-09 18:49:04 -08:00
"errors"
2014-10-13 20:07:26 -07:00
"io"
2015-08-02 22:20:49 -04:00
"github.com/tendermint/tendermint/Godeps/_workspace/src/golang.org/x/crypto/ripemd160"
2015-07-19 09:40:55 -07:00
acm "github.com/tendermint/tendermint/account"
2015-04-01 17:30:16 -07:00
. "github.com/tendermint/tendermint/common"
2015-07-20 14:24:57 -04:00
ptypes "github.com/tendermint/tendermint/permission/types"
2015-08-02 22:20:49 -04:00
"github.com/tendermint/tendermint/wire"
2014-06-05 02:34:45 -07:00
)
2014-12-23 01:35:54 -08:00
var (
2015-03-18 02:12:03 -07:00
ErrTxInvalidAddress = errors . New ( "Error invalid address" )
ErrTxDuplicateAddress = errors . New ( "Error duplicate address" )
ErrTxInvalidAmount = errors . New ( "Error invalid amount" )
ErrTxInsufficientFunds = errors . New ( "Error insufficient funds" )
ErrTxInsufficientGasPrice = errors . New ( "Error insufficient gas price" )
ErrTxUnknownPubKey = errors . New ( "Error unknown pubkey" )
ErrTxInvalidPubKey = errors . New ( "Error invalid pubkey" )
ErrTxInvalidSignature = errors . New ( "Error invalid signature" )
2015-07-24 12:32:22 -07:00
ErrTxPermissionDenied = errors . New ( "Error permission denied" )
2014-12-23 01:35:54 -08:00
)
2015-09-15 18:39:01 -04:00
type ErrTxInvalidString struct {
Msg string
}
func ( e ErrTxInvalidString ) Error ( ) string {
return e . Msg
}
2015-03-21 17:20:34 -07:00
type ErrTxInvalidSequence struct {
2015-06-25 20:28:34 -07:00
Got int
Expected int
2015-03-21 17:20:34 -07:00
}
func ( e ErrTxInvalidSequence ) Error ( ) string {
return Fmt ( "Error invalid sequence. Got %d, expected %d" , e . Got , e . Expected )
}
2014-06-05 02:34:45 -07:00
/ *
2014-12-09 18:49:04 -08:00
Tx ( Transaction ) is an atomic operation on the ledger state .
2014-09-10 02:43:16 -07:00
Account Txs :
2014-12-23 01:35:54 -08:00
- SendTx Send coins to address
2015-03-20 05:47:52 -07:00
- CallTx Send a msg to a contract that runs in the vm
2015-05-22 16:53:10 -04:00
- NameTx Store some value under a name in the global namereg
2014-09-10 02:43:16 -07:00
2014-09-11 01:11:41 -07:00
Validation Txs :
2014-12-23 01:35:54 -08:00
- BondTx New validator posts a bond
- UnbondTx Validator leaves
- DupeoutTx Validator dupes out ( equivocates )
2015-07-20 14:24:57 -04:00
Admin Txs :
2015-07-21 17:27:47 -04:00
- PermissionsTx
2014-06-05 02:34:45 -07:00
* /
2015-07-20 14:24:57 -04:00
2014-06-05 02:34:45 -07:00
type Tx interface {
2015-05-29 17:53:57 -04:00
WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error )
2014-06-05 02:34:45 -07:00
}
2014-12-23 01:35:54 -08:00
// Types of Tx implementations
2014-06-05 02:34:45 -07:00
const (
2014-09-10 02:43:16 -07:00
// Account transactions
2014-10-07 23:11:04 -07:00
TxTypeSend = byte ( 0x01 )
2015-03-18 01:27:16 -07:00
TxTypeCall = byte ( 0x02 )
2015-05-22 16:53:10 -04:00
TxTypeName = byte ( 0x03 )
2014-09-10 02:43:16 -07:00
2014-09-11 01:11:41 -07:00
// Validation transactions
2014-10-07 23:11:04 -07:00
TxTypeBond = byte ( 0x11 )
TxTypeUnbond = byte ( 0x12 )
2014-12-16 05:40:17 -08:00
TxTypeRebond = byte ( 0x13 )
TxTypeDupeout = byte ( 0x14 )
2015-07-20 14:24:57 -04:00
// Admin transactions
2015-07-21 17:27:47 -04:00
TxTypePermissions = byte ( 0x20 )
2014-06-05 02:34:45 -07:00
)
2015-07-25 15:45:45 -07:00
// for wire.readReflect
var _ = wire . RegisterInterface (
2015-01-04 17:33:18 -08:00
struct { Tx } { } ,
2015-07-25 15:45:45 -07:00
wire . ConcreteType { & SendTx { } , TxTypeSend } ,
wire . ConcreteType { & CallTx { } , TxTypeCall } ,
wire . ConcreteType { & NameTx { } , TxTypeName } ,
wire . ConcreteType { & BondTx { } , TxTypeBond } ,
wire . ConcreteType { & UnbondTx { } , TxTypeUnbond } ,
wire . ConcreteType { & RebondTx { } , TxTypeRebond } ,
wire . ConcreteType { & DupeoutTx { } , TxTypeDupeout } ,
wire . ConcreteType { & PermissionsTx { } , TxTypePermissions } ,
2015-01-04 17:33:18 -08:00
)
2014-12-09 18:49:04 -08:00
2014-09-10 02:43:16 -07:00
//-----------------------------------------------------------------------------
2014-06-05 02:34:45 -07:00
2014-12-09 18:49:04 -08:00
type TxInput struct {
2015-07-19 09:40:55 -07:00
Address [ ] byte ` json:"address" ` // Hash of the PubKey
Amount int64 ` json:"amount" ` // Must not exceed account balance
Sequence int ` json:"sequence" ` // Must be 1 greater than the last committed TxInput
Signature acm . Signature ` json:"signature" ` // Depends on the PubKey type and the whole Tx
PubKey acm . PubKey ` json:"pub_key" ` // Must not be nil, may be nil
2014-09-10 16:56:02 -07:00
}
2014-12-09 18:49:04 -08:00
func ( txIn * TxInput ) ValidateBasic ( ) error {
if len ( txIn . Address ) != 20 {
return ErrTxInvalidAddress
2014-09-10 16:56:02 -07:00
}
2014-12-09 18:49:04 -08:00
if txIn . Amount == 0 {
return ErrTxInvalidAmount
}
return nil
2014-09-10 16:56:02 -07:00
}
2014-12-09 18:49:04 -08:00
func ( txIn * TxInput ) WriteSignBytes ( w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "address":"%X","amount":%v,"sequence":%v} ` , txIn . Address , txIn . Amount , txIn . Sequence ) ) , w , n , err )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( txIn * TxInput ) String ( ) string {
return Fmt ( "TxInput{%X,%v,%v,%v,%v}" , txIn . Address , txIn . Amount , txIn . Sequence , txIn . Signature , txIn . PubKey )
}
2014-09-10 16:56:02 -07:00
//-----------------------------------------------------------------------------
2014-12-09 18:49:04 -08:00
type TxOutput struct {
2015-05-01 17:26:49 -07:00
Address [ ] byte ` json:"address" ` // Hash of the PubKey
2015-06-25 20:28:34 -07:00
Amount int64 ` json:"amount" ` // The sum of all outputs must not exceed the inputs.
2014-06-05 02:34:45 -07:00
}
2014-12-09 18:49:04 -08:00
func ( txOut * TxOutput ) ValidateBasic ( ) error {
if len ( txOut . Address ) != 20 {
return ErrTxInvalidAddress
}
if txOut . Amount == 0 {
return ErrTxInvalidAmount
}
return nil
2014-06-05 02:34:45 -07:00
}
2014-12-09 18:49:04 -08:00
func ( txOut * TxOutput ) WriteSignBytes ( w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "address":"%X","amount":%v} ` , txOut . Address , txOut . Amount ) ) , w , n , err )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( txOut * TxOutput ) String ( ) string {
return Fmt ( "TxOutput{%X,%v}" , txOut . Address , txOut . Amount )
}
2014-09-10 02:43:16 -07:00
//-----------------------------------------------------------------------------
2014-06-05 02:34:45 -07:00
2014-12-09 18:49:04 -08:00
type SendTx struct {
2015-05-01 17:26:49 -07:00
Inputs [ ] * TxInput ` json:"inputs" `
Outputs [ ] * TxOutput ` json:"outputs" `
2014-06-05 02:34:45 -07:00
}
2015-05-29 17:53:57 -04:00
func ( tx * SendTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "inputs":[ ` , TxTypeSend ) ) , w , n , err )
2015-04-25 19:42:20 -07:00
for i , in := range tx . Inputs {
2014-12-09 18:49:04 -08:00
in . WriteSignBytes ( w , n , err )
2015-04-25 19:42:20 -07:00
if i != len ( tx . Inputs ) - 1 {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( "," ) , w , n , err )
2015-04-25 19:42:20 -07:00
}
2014-12-09 18:49:04 -08:00
}
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( ` ],"outputs":[ ` ) , w , n , err )
2015-04-25 19:42:20 -07:00
for i , out := range tx . Outputs {
2014-12-09 18:49:04 -08:00
out . WriteSignBytes ( w , n , err )
2015-04-25 19:42:20 -07:00
if i != len ( tx . Outputs ) - 1 {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( "," ) , w , n , err )
2015-04-25 19:42:20 -07:00
}
2014-12-09 18:49:04 -08:00
}
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( ` ]}]} ` ) , w , n , err )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( tx * SendTx ) String ( ) string {
return Fmt ( "SendTx{%v -> %v}" , tx . Inputs , tx . Outputs )
}
2014-09-10 02:43:16 -07:00
//-----------------------------------------------------------------------------
2015-03-18 01:27:16 -07:00
type CallTx struct {
2015-05-01 17:26:49 -07:00
Input * TxInput ` json:"input" `
Address [ ] byte ` json:"address" `
2015-06-25 20:28:34 -07:00
GasLimit int64 ` json:"gas_limit" `
Fee int64 ` json:"fee" `
2015-05-01 17:26:49 -07:00
Data [ ] byte ` json:"data" `
2015-03-18 01:27:16 -07:00
}
2015-05-29 17:53:57 -04:00
func ( tx * CallTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "address":"%X","data":"%X" ` , TxTypeCall , tx . Address , tx . Data ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"fee":%v,"gas_limit":%v,"input": ` , tx . Fee , tx . GasLimit ) ) , w , n , err )
2015-03-18 01:27:16 -07:00
tx . Input . WriteSignBytes ( w , n , err )
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( ` }]} ` ) , w , n , err )
2015-03-18 01:27:16 -07:00
}
func ( tx * CallTx ) String ( ) string {
return Fmt ( "CallTx{%v -> %x: %x}" , tx . Input , tx . Address , tx . Data )
}
2015-08-02 22:20:49 -04:00
func NewContractAddress ( caller [ ] byte , nonce int ) [ ] byte {
temp := make ( [ ] byte , 32 + 8 )
copy ( temp , caller )
PutInt64BE ( temp [ 32 : ] , int64 ( nonce ) )
hasher := ripemd160 . New ( )
hasher . Write ( temp ) // does not error
return hasher . Sum ( nil )
}
2015-03-18 01:27:16 -07:00
//-----------------------------------------------------------------------------
2015-05-22 16:53:10 -04:00
type NameTx struct {
Input * TxInput ` json:"input" `
2015-05-23 02:05:56 -04:00
Name string ` json:"name" `
Data string ` json:"data" `
2015-06-25 20:28:34 -07:00
Fee int64 ` json:"fee" `
2015-05-22 16:53:10 -04:00
}
2015-05-30 01:54:05 -04:00
func ( tx * NameTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "data":%s,"fee":%v ` , TxTypeName , jsonEscape ( tx . Data ) , tx . Fee ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( ` ,"input": ` ) , w , n , err )
2015-05-22 16:53:10 -04:00
tx . Input . WriteSignBytes ( w , n , err )
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"name":%s ` , jsonEscape ( tx . Name ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( ` }]} ` ) , w , n , err )
2015-05-22 16:53:10 -04:00
}
2015-05-24 14:41:42 -04:00
func ( tx * NameTx ) ValidateStrings ( ) error {
if len ( tx . Name ) == 0 {
2015-09-15 18:39:01 -04:00
return ErrTxInvalidString { "Name must not be empty" }
2015-05-24 14:41:42 -04:00
}
if len ( tx . Name ) > MaxNameLength {
2015-09-15 18:39:01 -04:00
return ErrTxInvalidString { Fmt ( "Name is too long. Max %d bytes" , MaxNameLength ) }
2015-05-24 14:41:42 -04:00
}
if len ( tx . Data ) > MaxDataLength {
2015-09-15 18:39:01 -04:00
return ErrTxInvalidString { Fmt ( "Data is too long. Max %d bytes" , MaxDataLength ) }
2015-05-24 14:41:42 -04:00
}
if ! validateNameRegEntryName ( tx . Name ) {
2015-09-15 18:39:01 -04:00
return ErrTxInvalidString { Fmt ( "Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed" , tx . Name ) }
2015-05-24 14:41:42 -04:00
}
2015-05-23 02:05:56 -04:00
2015-05-24 14:41:42 -04:00
if ! validateNameRegEntryData ( tx . Data ) {
2015-09-15 18:39:01 -04:00
return ErrTxInvalidString { Fmt ( "Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed" , tx . Data ) }
2015-05-24 14:41:42 -04:00
}
return nil
}
2015-05-23 02:05:56 -04:00
2015-05-22 16:53:10 -04:00
func ( tx * NameTx ) String ( ) string {
return Fmt ( "NameTx{%v -> %s: %s}" , tx . Input , tx . Name , tx . Data )
}
//-----------------------------------------------------------------------------
2014-09-10 02:43:16 -07:00
type BondTx struct {
2015-07-19 09:40:55 -07:00
PubKey acm . PubKeyEd25519 ` json:"pub_key" `
Signature acm . SignatureEd25519 ` json:"signature" `
Inputs [ ] * TxInput ` json:"inputs" `
UnbondTo [ ] * TxOutput ` json:"unbond_to" `
2014-09-10 02:43:16 -07:00
}
2015-05-29 17:53:57 -04:00
func ( tx * BondTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "inputs":[ ` , TxTypeBond ) ) , w , n , err )
2015-04-25 19:42:20 -07:00
for i , in := range tx . Inputs {
2014-12-09 18:49:04 -08:00
in . WriteSignBytes ( w , n , err )
2015-04-25 19:42:20 -07:00
if i != len ( tx . Inputs ) - 1 {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( "," ) , w , n , err )
2015-04-25 19:42:20 -07:00
}
2014-12-09 18:49:04 -08:00
}
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` ],"pub_key": ` ) ) , w , n , err )
wire . WriteTo ( wire . JSONBytes ( tx . PubKey ) , w , n , err )
wire . WriteTo ( [ ] byte ( ` ,"unbond_to":[ ` ) , w , n , err )
2015-04-25 19:42:20 -07:00
for i , out := range tx . UnbondTo {
2014-12-09 18:49:04 -08:00
out . WriteSignBytes ( w , n , err )
2015-04-25 19:42:20 -07:00
if i != len ( tx . UnbondTo ) - 1 {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( "," ) , w , n , err )
2015-04-25 19:42:20 -07:00
}
2014-12-09 18:49:04 -08:00
}
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( ` ]}]} ` ) , w , n , err )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( tx * BondTx ) String ( ) string {
return Fmt ( "BondTx{%v: %v -> %v}" , tx . PubKey , tx . Inputs , tx . UnbondTo )
}
2014-09-10 02:43:16 -07:00
//-----------------------------------------------------------------------------
type UnbondTx struct {
2015-07-19 09:40:55 -07:00
Address [ ] byte ` json:"address" `
Height int ` json:"height" `
Signature acm . SignatureEd25519 ` json:"signature" `
2014-09-10 02:43:16 -07:00
}
2015-05-29 17:53:57 -04:00
func ( tx * UnbondTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "address":"%X","height":%v}]} ` , TxTypeUnbond , tx . Address , tx . Height ) ) , w , n , err )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( tx * UnbondTx ) String ( ) string {
return Fmt ( "UnbondTx{%X,%v,%v}" , tx . Address , tx . Height , tx . Signature )
}
2014-09-10 02:43:16 -07:00
//-----------------------------------------------------------------------------
2014-12-16 05:40:17 -08:00
type RebondTx struct {
2015-07-19 09:40:55 -07:00
Address [ ] byte ` json:"address" `
Height int ` json:"height" `
Signature acm . SignatureEd25519 ` json:"signature" `
2014-12-16 05:40:17 -08:00
}
2015-05-29 17:53:57 -04:00
func ( tx * RebondTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "address":"%X","height":%v}]} ` , TxTypeRebond , tx . Address , tx . Height ) ) , w , n , err )
2014-12-16 05:40:17 -08:00
}
2015-01-16 00:31:34 -08:00
func ( tx * RebondTx ) String ( ) string {
return Fmt ( "RebondTx{%X,%v,%v}" , tx . Address , tx . Height , tx . Signature )
}
2014-12-16 05:40:17 -08:00
//-----------------------------------------------------------------------------
2014-09-10 02:43:16 -07:00
type DupeoutTx struct {
2015-05-01 17:26:49 -07:00
Address [ ] byte ` json:"address" `
VoteA Vote ` json:"vote_a" `
VoteB Vote ` json:"vote_b" `
2014-09-10 02:43:16 -07:00
}
2015-05-29 17:53:57 -04:00
func ( tx * DupeoutTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-19 23:42:52 +00:00
PanicSanity ( "DupeoutTx has no sign bytes" )
2014-10-13 20:07:26 -07:00
}
2015-01-16 00:31:34 -08:00
func ( tx * DupeoutTx ) String ( ) string {
return Fmt ( "DupeoutTx{%X,%v,%v}" , tx . Address , tx . VoteA , tx . VoteB )
}
2015-03-29 18:43:27 -07:00
//-----------------------------------------------------------------------------
2015-07-21 17:27:47 -04:00
type PermissionsTx struct {
Input * TxInput ` json:"input" `
PermArgs ptypes . PermArgs ` json:"args" `
2015-07-20 14:24:57 -04:00
}
2015-07-21 17:27:47 -04:00
func ( tx * PermissionsTx ) WriteSignBytes ( chainID string , w io . Writer , n * int64 , err * error ) {
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( Fmt ( ` { "chain_id":%s ` , jsonEscape ( chainID ) ) ) , w , n , err )
wire . WriteTo ( [ ] byte ( Fmt ( ` ,"tx":[%v, { "args":" ` , TxTypePermissions ) ) , w , n , err )
wire . WriteJSON ( tx . PermArgs , w , n , err )
wire . WriteTo ( [ ] byte ( ` ","input": ` ) , w , n , err )
2015-07-20 14:24:57 -04:00
tx . Input . WriteSignBytes ( w , n , err )
2015-07-25 15:45:45 -07:00
wire . WriteTo ( [ ] byte ( ` }]} ` ) , w , n , err )
2015-07-20 14:24:57 -04:00
}
2015-07-21 17:27:47 -04:00
func ( tx * PermissionsTx ) String ( ) string {
return Fmt ( "PermissionsTx{%v -> %v}" , tx . Input , tx . PermArgs )
2015-07-20 14:24:57 -04:00
}
//-----------------------------------------------------------------------------
2015-07-10 12:15:46 -07:00
// This should match the leaf hashes of Block.Data.Hash()'s SimpleMerkleTree.
2015-07-10 05:56:38 +00:00
func TxID ( chainID string , tx Tx ) [ ] byte {
2015-07-19 09:40:55 -07:00
signBytes := acm . SignBytes ( chainID , tx )
2015-07-25 15:45:45 -07:00
return wire . BinaryRipemd160 ( signBytes )
2015-03-29 18:43:27 -07:00
}
2015-06-01 14:59:10 -07:00
//--------------------------------------------------------------------------------
// Contract: This function is deterministic and completely reversible.
func jsonEscape ( str string ) string {
escapedBytes , err := json . Marshal ( str )
if err != nil {
2015-07-19 23:42:52 +00:00
PanicSanity ( Fmt ( "Error json-escaping a string" , str ) )
2015-06-01 14:59:10 -07:00
}
return string ( escapedBytes )
}