cosmos-sdk/types/tx.go

232 lines
6.1 KiB
Go

package types
import (
"bytes"
"encoding/json"
abci "github.com/tendermint/abci/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-data"
"github.com/tendermint/go-wire"
)
/*
Tx (Transaction) is an atomic operation on the ledger state.
Account Types:
- SendTx Send coins to address
- AppTx Send a msg to a contract that runs in the vm
*/
type Tx interface {
AssertIsTx()
SignBytes(chainID string) []byte
}
// Types of Tx implementations
const (
// Account transactions
TxTypeSend = byte(0x01)
TxTypeApp = byte(0x02)
TxNameSend = "send"
TxNameApp = "app"
)
func (_ *SendTx) AssertIsTx() {}
func (_ *AppTx) AssertIsTx() {}
var txMapper data.Mapper
// register both private key types with go-data (and thus go-wire)
func init() {
txMapper = data.NewMapper(TxS{}).
RegisterImplementation(&SendTx{}, TxNameSend, TxTypeSend).
RegisterImplementation(&AppTx{}, TxNameApp, TxTypeApp)
}
// TxS add json serialization to Tx
type TxS struct {
Tx
}
func (p TxS) MarshalJSON() ([]byte, error) {
return txMapper.ToJSON(p.Tx)
}
func (p *TxS) UnmarshalJSON(data []byte) (err error) {
parsed, err := txMapper.FromJSON(data)
if err == nil {
p.Tx = parsed.(Tx)
}
return
}
//-----------------------------------------------------------------------------
type TxInput struct {
Address data.Bytes `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
Signature crypto.SignatureS `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKeyS `json:"pub_key"` // Is present iff Sequence == 0
}
func (txIn TxInput) ValidateBasic() abci.Result {
if len(txIn.Address) != 20 {
return abci.ErrBaseInvalidInput.AppendLog("Invalid address length")
}
if !txIn.Coins.IsValid() {
return abci.ErrBaseInvalidInput.AppendLog(Fmt("Invalid coins %v", txIn.Coins))
}
if txIn.Coins.IsZero() {
return abci.ErrBaseInvalidInput.AppendLog("Coins cannot be zero")
}
if txIn.Sequence <= 0 {
return abci.ErrBaseInvalidInput.AppendLog("Sequence must be greater than 0")
}
if txIn.Sequence == 1 && txIn.PubKey.Empty() {
return abci.ErrBaseInvalidInput.AppendLog("PubKey must be present when Sequence == 1")
}
if txIn.Sequence > 1 && !txIn.PubKey.Empty() {
return abci.ErrBaseInvalidInput.AppendLog("PubKey must be nil when Sequence > 1")
}
return abci.OK
}
func (txIn TxInput) String() string {
return Fmt("TxInput{%X,%v,%v,%v,%v}", txIn.Address, txIn.Coins, txIn.Sequence, txIn.Signature, txIn.PubKey)
}
func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput {
input := TxInput{
Address: pubKey.Address(),
Coins: coins,
Sequence: sequence,
}
if sequence == 1 {
// safely wrap if needed
// TODO: extract this as utility function?
ps, ok := pubKey.(crypto.PubKeyS)
if !ok {
ps = crypto.PubKeyS{pubKey}
}
input.PubKey = ps
}
return input
}
//-----------------------------------------------------------------------------
type TxOutput struct {
Address data.Bytes `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` //
}
func (txOut TxOutput) ValidateBasic() abci.Result {
if len(txOut.Address) != 20 {
return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
}
if !txOut.Coins.IsValid() {
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
}
if txOut.Coins.IsZero() {
return abci.ErrBaseInvalidOutput.AppendLog("Coins cannot be zero")
}
return abci.OK
}
func (txOut TxOutput) String() string {
return Fmt("TxOutput{%X,%v}", txOut.Address, txOut.Coins)
}
//-----------------------------------------------------------------------------
type SendTx struct {
Gas int64 `json:"gas"` // Gas
Fee Coin `json:"fee"` // Fee
Inputs []TxInput `json:"inputs"`
Outputs []TxOutput `json:"outputs"`
}
func (tx *SendTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID)
sigz := make([]crypto.Signature, len(tx.Inputs))
for i, input := range tx.Inputs {
sigz[i] = input.Signature.Signature
tx.Inputs[i].Signature.Signature = nil
}
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
for i := range tx.Inputs {
tx.Inputs[i].Signature.Signature = sigz[i]
}
return signBytes
}
func (tx *SendTx) SetSignature(addr []byte, sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
for i, input := range tx.Inputs {
if bytes.Equal(input.Address, addr) {
tx.Inputs[i].Signature = sigs
return true
}
}
return false
}
func (tx *SendTx) String() string {
return Fmt("SendTx{%v/%v %v->%v}", tx.Gas, tx.Fee, tx.Inputs, tx.Outputs)
}
//-----------------------------------------------------------------------------
type AppTx struct {
Gas int64 `json:"gas"` // Gas
Fee Coin `json:"fee"` // Fee
Name string `json:"type"` // Which plugin
Input TxInput `json:"input"` // Hmmm do we want coins?
Data json.RawMessage `json:"data"`
}
func (tx *AppTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID)
sig := tx.Input.Signature
tx.Input.Signature.Signature = nil
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
tx.Input.Signature = sig
return signBytes
}
func (tx *AppTx) SetSignature(sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
tx.Input.Signature = sigs
return true
}
func (tx *AppTx) String() string {
return Fmt("AppTx{%v/%v %v %v %X}", tx.Gas, tx.Fee, tx.Name, tx.Input, tx.Data)
}
//-----------------------------------------------------------------------------
func TxID(chainID string, tx Tx) []byte {
signBytes := tx.SignBytes(chainID)
return wire.BinaryRipemd160(signBytes)
}
//--------------------------------------------------------------------------------
// Contract: This function is deterministic and completely reversible.
func jsonEscape(str string) string {
escapedBytes, err := json.Marshal(str)
if err != nil {
PanicSanity(Fmt("Error json-escaping a string", str))
}
return string(escapedBytes)
}