From 4dd913a509259b67dd9d0e37957325dc5605b90d Mon Sep 17 00:00:00 2001 From: Silas Davis <silas@monax.io> Date: Tue, 3 Apr 2018 22:22:20 +0100 Subject: [PATCH] Refactor tx types into individual files Signed-off-by: Silas Davis <silas@monax.io> --- execution/transactor.go | 24 +-- txs/README.md | 61 ------- txs/bond_tx.go | 105 +++++++++++ txs/call_tx.go | 81 +++++++++ txs/name_tx.go | 114 ++++++++++++ txs/names.go | 18 -- txs/permission_tx.go | 72 ++++++++ txs/rebond_tx.go | 46 +++++ txs/send_tx.go | 96 ++++++++++ txs/tx.go | 388 +++++----------------------------------- txs/tx_input.go | 35 ++++ txs/tx_output.go | 32 ++++ txs/tx_utils.go | 272 ---------------------------- txs/unbond_tx.go | 46 +++++ 14 files changed, 679 insertions(+), 711 deletions(-) delete mode 100644 txs/README.md create mode 100644 txs/bond_tx.go create mode 100644 txs/call_tx.go create mode 100644 txs/name_tx.go create mode 100644 txs/permission_tx.go create mode 100644 txs/rebond_tx.go create mode 100644 txs/send_tx.go create mode 100644 txs/tx_input.go create mode 100644 txs/tx_output.go delete mode 100644 txs/tx_utils.go create mode 100644 txs/unbond_tx.go diff --git a/execution/transactor.go b/execution/transactor.go index 7d0a81c7..952aaab0 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -375,40 +375,40 @@ func (trans *Transactor) TransactNameReg(privKey []byte, name, data string, amou } // Sign a transaction -func (trans *Transactor) SignTx(tx txs.Tx, privAccounts []acm.SigningAccount) (txs.Tx, error) { +func (trans *Transactor) SignTx(tx txs.Tx, signingAccounts []acm.SigningAccount) (txs.Tx, error) { // more checks? chainID := trans.tip.ChainID() switch tx.(type) { case *txs.NameTx: nameTx := tx.(*txs.NameTx) - nameTx.Input.PublicKey = privAccounts[0].PublicKey() - nameTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, nameTx) + nameTx.Input.PublicKey = signingAccounts[0].PublicKey() + nameTx.Input.Signature = acm.ChainSign(signingAccounts[0], chainID, nameTx) case *txs.SendTx: sendTx := tx.(*txs.SendTx) for i, input := range sendTx.Inputs { - input.PublicKey = privAccounts[i].PublicKey() - input.Signature = acm.ChainSign(privAccounts[i], chainID, sendTx) + input.PublicKey = signingAccounts[i].PublicKey() + input.Signature = acm.ChainSign(signingAccounts[i], chainID, sendTx) } case *txs.CallTx: callTx := tx.(*txs.CallTx) - callTx.Input.PublicKey = privAccounts[0].PublicKey() - callTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, callTx) + callTx.Input.PublicKey = signingAccounts[0].PublicKey() + callTx.Input.Signature = acm.ChainSign(signingAccounts[0], chainID, callTx) case *txs.BondTx: bondTx := tx.(*txs.BondTx) // the first privaccount corresponds to the BondTx pub key. // the rest to the inputs - bondTx.Signature = acm.ChainSign(privAccounts[0], chainID, bondTx) + bondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, bondTx) for i, input := range bondTx.Inputs { - input.PublicKey = privAccounts[i+1].PublicKey() - input.Signature = acm.ChainSign(privAccounts[i+1], chainID, bondTx) + input.PublicKey = signingAccounts[i+1].PublicKey() + input.Signature = acm.ChainSign(signingAccounts[i+1], chainID, bondTx) } case *txs.UnbondTx: unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = acm.ChainSign(privAccounts[0], chainID, unbondTx) + unbondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, unbondTx) case *txs.RebondTx: rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = acm.ChainSign(privAccounts[0], chainID, rebondTx) + rebondTx.Signature = acm.ChainSign(signingAccounts[0], chainID, rebondTx) default: return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) } diff --git a/txs/README.md b/txs/README.md deleted file mode 100644 index f99294b0..00000000 --- a/txs/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# `tendermint/block` - -## Block - -TODO: document - -### Header - -### Validation - -### Data - -## PartSet - -PartSet is used to split a byteslice of data into parts (pieces) for transmission. -By splitting data into smaller parts and computing a Merkle root hash on the list, -you can verify that a part is legitimately part of the complete data, and the -part can be forwarded to other peers before all the parts are known. In short, -it's a fast way to propagate a large file over a gossip network. - -PartSet was inspired by the LibSwift project. - -Usage: - -```Go -data := RandBytes(2 << 20) // Something large - -partSet := NewPartSetFromData(data) -partSet.Total() // Total number of 4KB parts -partSet.Count() // Equal to the Total, since we already have all the parts -partSet.Hash() // The Merkle root hash -partSet.BitArray() // A BitArray of partSet.Total() 1's - -header := partSet.Header() // Send this to the peer -header.Total // Total number of parts -header.Hash // The merkle root hash - -// Now we'll reconstruct the data from the parts -partSet2 := NewPartSetFromHeader(header) -partSet2.Total() // Same total as partSet.Total() -partSet2.Count() // Zero, since this PartSet doesn't have any parts yet. -partSet2.Hash() // Same hash as in partSet.Hash() -partSet2.BitArray() // A BitArray of partSet.Total() 0's - -// In a gossip network the parts would arrive in arbitrary order, perhaps -// in response to explicit requests for parts, or optimistically in response -// to the receiving peer's partSet.BitArray(). -for !partSet2.IsComplete() { - part := receivePartFromGossipNetwork() - added, err := partSet2.AddPart(part) - if err != nil { - // A wrong part, - // the merkle trail does not hash to partSet2.Hash() - } else if !added { - // A duplicate part already received - } -} - -data2, _ := ioutil.ReadAll(partSet2.GetReader()) -bytes.Equal(data, data2) // true -``` diff --git a/txs/bond_tx.go b/txs/bond_tx.go new file mode 100644 index 00000000..cbb4bf8a --- /dev/null +++ b/txs/bond_tx.go @@ -0,0 +1,105 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type BondTx struct { + PubKey acm.PublicKey + Signature acm.Signature + Inputs []*TxInput + UnbondTo []*TxOutput + txHashMemoizer +} + +var _ Tx = &BondTx{} + +func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { + return &BondTx{ + PubKey: pubkey, + Inputs: []*TxInput{}, + UnbondTo: []*TxOutput{}, + }, nil +} + +func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) + for i, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + if i != len(tx.Inputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err) + wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err) + wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err) + for i, out := range tx.UnbondTo { + out.WriteSignBytes(w, n, err) + if i != len(tx.UnbondTo)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`]}]}`), w, n, err) +} + +func (tx *BondTx) GetInputs() []TxInput { + return copyInputs(tx.Inputs) +} + +func (tx *BondTx) String() string { + return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) +} + +func (tx *BondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { + addr := pubkey.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey) + } + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1)) +} + +func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: pubkey.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: pubkey, + }) + return nil +} + +func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error { + tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ + Address: addr, + Amount: amt, + }) + return nil +} + +func (tx *BondTx) SignBond(chainID string, privAccount acm.SigningAccount) error { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) + return nil +} + +func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error { + if i >= len(tx.Inputs) { + return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) + } + tx.Inputs[i].PublicKey = privAccount.PublicKey() + tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) + return nil +} diff --git a/txs/call_tx.go b/txs/call_tx.go new file mode 100644 index 00000000..ff568aef --- /dev/null +++ b/txs/call_tx.go @@ -0,0 +1,81 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type CallTx struct { + Input *TxInput + // Pointer since CallTx defines unset 'to' address as inducing account creation + Address *acm.Address + GasLimit uint64 + Fee uint64 + Data []byte + txHashMemoizer +} + +var _ Tx = &CallTx{} + +func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee uint64) (*CallTx, error) { + + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil +} + +func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte, + amt, gasLimit, fee, sequence uint64) *CallTx { + input := &TxInput{ + Address: from.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: from, + } + + return &CallTx{ + Input: input, + Address: to, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } +} + +func (tx *CallTx) Sign(chainID string, privAccount acm.SigningAccount) { + tx.Input.PublicKey = privAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) +} + +func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *CallTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *CallTx) String() string { + return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) +} + +func (tx *CallTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/name_tx.go b/txs/name_tx.go new file mode 100644 index 00000000..c00b6839 --- /dev/null +++ b/txs/name_tx.go @@ -0,0 +1,114 @@ +package txs + +import ( + "fmt" + "io" + + "regexp" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +// Name should be file system lik +// Data should be anything permitted in JSON +var regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$") +var regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`) + +type NameTx struct { + Input *TxInput + Name string + Data string + Fee uint64 + txHashMemoizer +} + +var _ Tx = &NameTx{} + +func NewNameTx(st state.AccountGetter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) { + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil +} + +func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx { + input := &TxInput{ + Address: from.Address(), + Amount: amt, + Sequence: sequence, + PublicKey: from, + } + + return &NameTx{ + Input: input, + Name: name, + Data: data, + Fee: fee, + } +} + +func (tx *NameTx) Sign(chainID string, signingAccount acm.SigningAccount) { + tx.Input.PublicKey = signingAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(signingAccount, chainID, tx) +} + +func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err) + wire.WriteTo([]byte(`,"input":`), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *NameTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *NameTx) ValidateStrings() error { + if len(tx.Name) == 0 { + return ErrTxInvalidString{"Name must not be empty"} + } + if len(tx.Name) > MaxNameLength { + return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)} + } + if len(tx.Data) > MaxDataLength { + return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)} + } + + if !validateNameRegEntryName(tx.Name) { + return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)} + } + + if !validateNameRegEntryData(tx.Data) { + return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)} + } + + return nil +} + +func (tx *NameTx) String() string { + return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) +} + +func (tx *NameTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +// filter strings +func validateNameRegEntryName(name string) bool { + return regexpAlphaNum.Match([]byte(name)) +} + +func validateNameRegEntryData(data string) bool { + return regexpJSON.Match([]byte(data)) +} diff --git a/txs/names.go b/txs/names.go index 23c693f0..3b0bceff 100644 --- a/txs/names.go +++ b/txs/names.go @@ -14,10 +14,6 @@ package txs -import ( - "regexp" -) - var ( MinNameRegistrationPeriod uint64 = 5 @@ -31,22 +27,8 @@ var ( MaxNameLength = 64 MaxDataLength = 1 << 16 - - // Name should be file system lik - // Data should be anything permitted in JSON - regexpAlphaNum = regexp.MustCompile("^[a-zA-Z0-9._/-@]*$") - regexpJSON = regexp.MustCompile(`^[a-zA-Z0-9_/ \-+"':,\n\t.{}()\[\]]*$`) ) -// filter strings -func validateNameRegEntryName(name string) bool { - return regexpAlphaNum.Match([]byte(name)) -} - -func validateNameRegEntryData(data string) bool { - return regexpJSON.Match([]byte(data)) -} - // base cost is "effective" number of bytes func NameBaseCost(name, data string) uint64 { return uint64(len(data) + 32) diff --git a/txs/permission_tx.go b/txs/permission_tx.go new file mode 100644 index 00000000..755dbfe3 --- /dev/null +++ b/txs/permission_tx.go @@ -0,0 +1,72 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/permission/snatives" + "github.com/tendermint/go-wire" +) + +type PermissionsTx struct { + Input *TxInput + PermArgs snatives.PermArgs + txHashMemoizer +} + +var _ Tx = &PermissionsTx{} + +func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) { + addr := from.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) + } + + sequence := acc.Sequence() + 1 + return NewPermissionsTxWithSequence(from, args, sequence), nil +} + +func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx { + input := &TxInput{ + Address: from.Address(), + Amount: 1, // NOTE: amounts can't be 0 ... + Sequence: sequence, + PublicKey: from, + } + + return &PermissionsTx{ + Input: input, + PermArgs: args, + } +} + +func (tx *PermissionsTx) Sign(chainID string, privAccount acm.SigningAccount) { + tx.Input.PublicKey = privAccount.PublicKey() + tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) +} +func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err) + wire.WriteJSON(&tx.PermArgs, w, n, err) + wire.WriteTo([]byte(`","input":`), w, n, err) + tx.Input.WriteSignBytes(w, n, err) + wire.WriteTo([]byte(`}]}`), w, n, err) +} + +func (tx *PermissionsTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *PermissionsTx) String() string { + return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) +} + +func (tx *PermissionsTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/rebond_tx.go b/txs/rebond_tx.go new file mode 100644 index 00000000..ec13498b --- /dev/null +++ b/txs/rebond_tx.go @@ -0,0 +1,46 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type RebondTx struct { + Address acm.Address + Height int + Signature acm.Signature + txHashMemoizer +} + +var _ Tx = &RebondTx{} + +func NewRebondTx(addr acm.Address, height int) *RebondTx { + return &RebondTx{ + Address: addr, + Height: height, + } +} + +func (tx *RebondTx) Sign(chainID string, privAccount acm.SigningAccount) { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) +} + +func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) +} + +func (tx *RebondTx) GetInputs() []TxInput { + return nil +} + +func (tx *RebondTx) String() string { + return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) +} + +func (tx *RebondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/send_tx.go b/txs/send_tx.go new file mode 100644 index 00000000..06c651a3 --- /dev/null +++ b/txs/send_tx.go @@ -0,0 +1,96 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/tendermint/go-wire" +) + +type SendTx struct { + Inputs []*TxInput + Outputs []*TxOutput + txHashMemoizer +} + +var _ Tx = &SendTx{} + +func NewSendTx() *SendTx { + return &SendTx{ + Inputs: []*TxInput{}, + Outputs: []*TxOutput{}, + } +} + +func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) + for i, in := range tx.Inputs { + in.WriteSignBytes(w, n, err) + if i != len(tx.Inputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`],"outputs":[`), w, n, err) + for i, out := range tx.Outputs { + out.WriteSignBytes(w, n, err) + if i != len(tx.Outputs)-1 { + wire.WriteTo([]byte(","), w, n, err) + } + } + wire.WriteTo([]byte(`]}]}`), w, n, err) +} + +func (tx *SendTx) GetInputs() []TxInput { + return copyInputs(tx.Inputs) +} + +func (tx *SendTx) String() string { + return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) +} + +func (tx *SendTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} + +func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { + addr := pubkey.Address() + acc, err := st.GetAccount(addr) + if err != nil { + return err + } + if acc == nil { + return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey) + } + return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1) +} + +func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { + addr := pubkey.Address() + tx.Inputs = append(tx.Inputs, &TxInput{ + Address: addr, + Amount: amt, + Sequence: sequence, + PublicKey: pubkey, + }) + return nil +} + +func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error { + tx.Outputs = append(tx.Outputs, &TxOutput{ + Address: addr, + Amount: amt, + }) + return nil +} + +func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error { + if i >= len(tx.Inputs) { + return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) + } + tx.Inputs[i].PublicKey = privAccount.PublicKey() + tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) + return nil +} diff --git a/txs/tx.go b/txs/tx.go index e79c1e45..bfcf3ac1 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -21,8 +21,6 @@ import ( "io" acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/permission/snatives" - "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" "golang.org/x/crypto/ripemd160" ) @@ -37,23 +35,6 @@ var ( ErrTxInvalidSignature = errors.New("error invalid signature") ) -type ErrTxInvalidString struct { - Msg string -} - -func (e ErrTxInvalidString) Error() string { - return e.Msg -} - -type ErrTxInvalidSequence struct { - Got uint64 - Expected uint64 -} - -func (e ErrTxInvalidSequence) Error() string { - return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) -} - /* Tx (Transaction) is an atomic operation on the ledger state. @@ -95,104 +76,34 @@ var mapper = data.NewMapper(Wrapper{}). RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond). RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions) -//----------------------------------------------------------------------------- - -type ( - // TODO: replace with sum-type struct like ResultEvent - Tx interface { - WriteSignBytes(chainID string, w io.Writer, n *int, err *error) - String() string - GetInputs() []TxInput - Hash(chainID string) []byte - } - - Wrapper struct { - Tx `json:"unwrap"` - } + //----------------------------------------------------------------------------- - Encoder interface { - EncodeTx(tx Tx) ([]byte, error) - } - - Decoder interface { - DecodeTx(txBytes []byte) (Tx, error) - } - - TxInput struct { - Address acm.Address - Amount uint64 - Sequence uint64 - Signature acm.Signature - PublicKey acm.PublicKey - } - - TxOutput struct { - Address acm.Address - Amount uint64 - } - - // BroadcastTx or Transact - Receipt struct { - TxHash []byte - CreatesContract bool - ContractAddress acm.Address - } - - //------------------- - // Transaction Types - SendTx struct { - Inputs []*TxInput - Outputs []*TxOutput - txHashMemoizer - } - - NameTx struct { - Input *TxInput - Name string - Data string - Fee uint64 - txHashMemoizer - } - - CallTx struct { - Input *TxInput - // Pointer since CallTx defines unset 'to' address as inducing account creation - Address *acm.Address - GasLimit uint64 - Fee uint64 - Data []byte - txHashMemoizer - } +// TODO: replace with sum-type struct like ResultEvent +type Tx interface { + WriteSignBytes(chainID string, w io.Writer, n *int, err *error) + String() string + GetInputs() []TxInput + Hash(chainID string) []byte +} - PermissionsTx struct { - Input *TxInput - PermArgs snatives.PermArgs - txHashMemoizer - } +type Encoder interface { + EncodeTx(tx Tx) ([]byte, error) +} - // Out of service - BondTx struct { - PubKey acm.PublicKey - Signature acm.Signature - Inputs []*TxInput - UnbondTo []*TxOutput - txHashMemoizer - } +type Decoder interface { + DecodeTx(txBytes []byte) (Tx, error) +} - UnbondTx struct { - Address acm.Address - Height int - Signature acm.Signature - txHashMemoizer - } +// BroadcastTx or Transact +type Receipt struct { + TxHash []byte + CreatesContract bool + ContractAddress acm.Address +} - RebondTx struct { - Address acm.Address - Height int - Signature acm.Signature - txHashMemoizer - } -) +type Wrapper struct { + Tx `json:"unwrap"` +} // Wrap the Tx in a struct that allows for go-wire JSON serialisation func Wrap(tx Tx) Wrapper { @@ -226,242 +137,6 @@ func (txw *Wrapper) Unwrap() Tx { return txw.Tx } -func (txIn *TxInput) ValidateBasic() error { - if len(txIn.Address) != 20 { - return ErrTxInvalidAddress - } - if txIn.Amount == 0 { - return ErrTxInvalidAmount - } - return nil -} - -func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err) -} - -func (txIn *TxInput) String() string { - return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey) -} - -//----------------------------------------------------------------------------- - -func (txOut *TxOutput) ValidateBasic() error { - if len(txOut.Address) != 20 { - return ErrTxInvalidAddress - } - if txOut.Amount == 0 { - return ErrTxInvalidAmount - } - return nil -} - -func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) -} - -func (txOut *TxOutput) String() string { - return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount) -} - -//----------------------------------------------------------------------------- - -func (tx *SendTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeSend)), w, n, err) - for i, in := range tx.Inputs { - in.WriteSignBytes(w, n, err) - if i != len(tx.Inputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`],"outputs":[`), w, n, err) - for i, out := range tx.Outputs { - out.WriteSignBytes(w, n, err) - if i != len(tx.Outputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`]}]}`), w, n, err) -} - -func (tx *SendTx) GetInputs() []TxInput { - return copyInputs(tx.Inputs) -} - -func (tx *SendTx) String() string { - return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) -} - -func (tx *SendTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *CallTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","data":"%X"`, TxTypeCall, tx.Address, tx.Data)), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"fee":%v,"gas_limit":%v,"input":`, tx.Fee, tx.GasLimit)), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *CallTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *CallTx) String() string { - return fmt.Sprintf("CallTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) -} - -func (tx *CallTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"data":%s,"fee":%v`, TxTypeName, jsonEscape(tx.Data), tx.Fee)), w, n, err) - wire.WriteTo([]byte(`,"input":`), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"name":%s`, jsonEscape(tx.Name))), w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *NameTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *NameTx) ValidateStrings() error { - if len(tx.Name) == 0 { - return ErrTxInvalidString{"Name must not be empty"} - } - if len(tx.Name) > MaxNameLength { - return ErrTxInvalidString{fmt.Sprintf("Name is too long. Max %d bytes", MaxNameLength)} - } - if len(tx.Data) > MaxDataLength { - return ErrTxInvalidString{fmt.Sprintf("Data is too long. Max %d bytes", MaxDataLength)} - } - - if !validateNameRegEntryName(tx.Name) { - return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Name (%s). Only alphanumeric, underscores, dashes, forward slashes, and @ are allowed", tx.Name)} - } - - if !validateNameRegEntryData(tx.Data) { - return ErrTxInvalidString{fmt.Sprintf("Invalid characters found in NameTx.Data (%s). Only the kind of things found in a JSON file are allowed", tx.Data)} - } - - return nil -} - -func (tx *NameTx) String() string { - return fmt.Sprintf("NameTx{%v -> %s: %s}", tx.Input, tx.Name, tx.Data) -} - -func (tx *NameTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *BondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"inputs":[`, TxTypeBond)), w, n, err) - for i, in := range tx.Inputs { - in.WriteSignBytes(w, n, err) - if i != len(tx.Inputs)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(fmt.Sprintf(`],"pub_key":`)), w, n, err) - wire.WriteTo(wire.JSONBytes(tx.PubKey), w, n, err) - wire.WriteTo([]byte(`,"unbond_to":[`), w, n, err) - for i, out := range tx.UnbondTo { - out.WriteSignBytes(w, n, err) - if i != len(tx.UnbondTo)-1 { - wire.WriteTo([]byte(","), w, n, err) - } - } - wire.WriteTo([]byte(`]}]}`), w, n, err) -} - -func (tx *BondTx) GetInputs() []TxInput { - return copyInputs(tx.Inputs) -} - -func (tx *BondTx) String() string { - return fmt.Sprintf("BondTx{%v: %v -> %v}", tx.PubKey, tx.Inputs, tx.UnbondTo) -} - -func (tx *BondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) -} - -func (tx *UnbondTx) GetInputs() []TxInput { - return nil -} - -func (tx *UnbondTx) String() string { - return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) -} - -func (tx *UnbondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *RebondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeRebond, tx.Address, tx.Height)), w, n, err) -} - -func (tx *RebondTx) GetInputs() []TxInput { - return nil -} - -func (tx *RebondTx) String() string { - return fmt.Sprintf("RebondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) -} - -func (tx *RebondTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - -func (tx *PermissionsTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) - wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"args":"`, TxTypePermissions)), w, n, err) - wire.WriteJSON(&tx.PermArgs, w, n, err) - wire.WriteTo([]byte(`","input":`), w, n, err) - tx.Input.WriteSignBytes(w, n, err) - wire.WriteTo([]byte(`}]}`), w, n, err) -} - -func (tx *PermissionsTx) GetInputs() []TxInput { - return []TxInput{*tx.Input} -} - -func (tx *PermissionsTx) String() string { - return fmt.Sprintf("PermissionsTx{%v -> %v}", tx.Input, tx.PermArgs) -} - -func (tx *PermissionsTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) -} - -//----------------------------------------------------------------------------- - // Avoid re-hashing the same in-memory Tx type txHashMemoizer struct { txHashBytes []byte @@ -499,6 +174,23 @@ func GenerateReceipt(chainId string, tx Tx) Receipt { return receipt } +type ErrTxInvalidString struct { + Msg string +} + +func (e ErrTxInvalidString) Error() string { + return e.Msg +} + +type ErrTxInvalidSequence struct { + Got uint64 + Expected uint64 +} + +func (e ErrTxInvalidSequence) Error() string { + return fmt.Sprintf("Error invalid sequence. Got %d, expected %d", e.Got, e.Expected) +} + //-------------------------------------------------------------------------------- func copyInputs(inputs []*TxInput) []TxInput { diff --git a/txs/tx_input.go b/txs/tx_input.go new file mode 100644 index 00000000..8ed9059d --- /dev/null +++ b/txs/tx_input.go @@ -0,0 +1,35 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type TxInput struct { + Address acm.Address + Amount uint64 + Sequence uint64 + Signature acm.Signature + PublicKey acm.PublicKey +} + +func (txIn *TxInput) ValidateBasic() error { + if len(txIn.Address) != 20 { + return ErrTxInvalidAddress + } + if txIn.Amount == 0 { + return ErrTxInvalidAmount + } + return nil +} + +func (txIn *TxInput) WriteSignBytes(w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)), w, n, err) +} + +func (txIn *TxInput) String() string { + return fmt.Sprintf("TxInput{%s,%v,%v,%v,%v}", txIn.Address, txIn.Amount, txIn.Sequence, txIn.Signature, txIn.PublicKey) +} diff --git a/txs/tx_output.go b/txs/tx_output.go new file mode 100644 index 00000000..79c17b49 --- /dev/null +++ b/txs/tx_output.go @@ -0,0 +1,32 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type TxOutput struct { + Address acm.Address + Amount uint64 +} + +func (txOut *TxOutput) ValidateBasic() error { + if len(txOut.Address) != 20 { + return ErrTxInvalidAddress + } + if txOut.Amount == 0 { + return ErrTxInvalidAmount + } + return nil +} + +func (txOut *TxOutput) WriteSignBytes(w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"address":"%s","amount":%v}`, txOut.Address, txOut.Amount)), w, n, err) +} + +func (txOut *TxOutput) String() string { + return fmt.Sprintf("TxOutput{%s,%v}", txOut.Address, txOut.Amount) +} diff --git a/txs/tx_utils.go b/txs/tx_utils.go deleted file mode 100644 index e1b8a8a4..00000000 --- a/txs/tx_utils.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package txs - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/account/state" - "github.com/hyperledger/burrow/permission/snatives" -) - -//---------------------------------------------------------------------------- -// SendTx interface for adding inputs/outputs and adding signatures - -func NewSendTx() *SendTx { - return &SendTx{ - Inputs: []*TxInput{}, - Outputs: []*TxOutput{}, - } -} - -func (tx *SendTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { - addr := pubkey.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return err - } - if acc == nil { - return fmt.Errorf("invalid address %s from pubkey %s", addr, pubkey) - } - return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+1) -} - -func (tx *SendTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { - addr := pubkey.Address() - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: addr, - Amount: amt, - Sequence: sequence, - PublicKey: pubkey, - }) - return nil -} - -func (tx *SendTx) AddOutput(addr acm.Address, amt uint64) error { - tx.Outputs = append(tx.Outputs, &TxOutput{ - Address: addr, - Amount: amt, - }) - return nil -} - -func (tx *SendTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error { - if i >= len(tx.Inputs) { - return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) - } - tx.Inputs[i].PublicKey = privAccount.PublicKey() - tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -//---------------------------------------------------------------------------- -// CallTx interface for creating tx - -func NewCallTx(st state.AccountGetter, from acm.PublicKey, to *acm.Address, data []byte, - amt, gasLimit, fee uint64) (*CallTx, error) { - - addr := from.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewCallTxWithSequence(from, to, data, amt, gasLimit, fee, sequence), nil -} - -func NewCallTxWithSequence(from acm.PublicKey, to *acm.Address, data []byte, - amt, gasLimit, fee, sequence uint64) *CallTx { - input := &TxInput{ - Address: from.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: from, - } - - return &CallTx{ - Input: input, - Address: to, - GasLimit: gasLimit, - Fee: fee, - Data: data, - } -} - -func (tx *CallTx) Sign(chainID string, privAccount acm.SigningAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// NameTx interface for creating tx - -func NewNameTx(st state.AccountGetter, from acm.PublicKey, name, data string, amt, fee uint64) (*NameTx, error) { - addr := from.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewNameTxWithSequence(from, name, data, amt, fee, sequence), nil -} - -func NewNameTxWithSequence(from acm.PublicKey, name, data string, amt, fee, sequence uint64) *NameTx { - input := &TxInput{ - Address: from.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: from, - } - - return &NameTx{ - Input: input, - Name: name, - Data: data, - Fee: fee, - } -} - -func (tx *NameTx) Sign(chainID string, privAccount acm.SigningAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// BondTx interface for adding inputs/outputs and adding signatures - -func NewBondTx(pubkey acm.PublicKey) (*BondTx, error) { - return &BondTx{ - PubKey: pubkey, - Inputs: []*TxInput{}, - UnbondTo: []*TxOutput{}, - }, nil -} - -func (tx *BondTx) AddInput(st state.AccountGetter, pubkey acm.PublicKey, amt uint64) error { - addr := pubkey.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return err - } - if acc == nil { - return fmt.Errorf("Invalid address %s from pubkey %s", addr, pubkey) - } - return tx.AddInputWithSequence(pubkey, amt, acc.Sequence()+uint64(1)) -} - -func (tx *BondTx) AddInputWithSequence(pubkey acm.PublicKey, amt uint64, sequence uint64) error { - tx.Inputs = append(tx.Inputs, &TxInput{ - Address: pubkey.Address(), - Amount: amt, - Sequence: sequence, - PublicKey: pubkey, - }) - return nil -} - -func (tx *BondTx) AddOutput(addr acm.Address, amt uint64) error { - tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ - Address: addr, - Amount: amt, - }) - return nil -} - -func (tx *BondTx) SignBond(chainID string, privAccount acm.SigningAccount) error { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -func (tx *BondTx) SignInput(chainID string, i int, privAccount acm.SigningAccount) error { - if i >= len(tx.Inputs) { - return fmt.Errorf("Index %v is greater than number of inputs (%v)", i, len(tx.Inputs)) - } - tx.Inputs[i].PublicKey = privAccount.PublicKey() - tx.Inputs[i].Signature = acm.ChainSign(privAccount, chainID, tx) - return nil -} - -//---------------------------------------------------------------------- -// UnbondTx interface for creating tx - -func NewUnbondTx(addr acm.Address, height int) *UnbondTx { - return &UnbondTx{ - Address: addr, - Height: height, - } -} - -func (tx *UnbondTx) Sign(chainID string, privAccount acm.SigningAccount) { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------- -// RebondTx interface for creating tx - -func NewRebondTx(addr acm.Address, height int) *RebondTx { - return &RebondTx{ - Address: addr, - Height: height, - } -} - -func (tx *RebondTx) Sign(chainID string, privAccount acm.SigningAccount) { - tx.Signature = acm.ChainSign(privAccount, chainID, tx) -} - -//---------------------------------------------------------------------------- -// PermissionsTx interface for creating tx - -func NewPermissionsTx(st state.AccountGetter, from acm.PublicKey, args snatives.PermArgs) (*PermissionsTx, error) { - addr := from.Address() - acc, err := st.GetAccount(addr) - if err != nil { - return nil, err - } - if acc == nil { - return nil, fmt.Errorf("Invalid address %s from pubkey %s", addr, from) - } - - sequence := acc.Sequence() + 1 - return NewPermissionsTxWithSequence(from, args, sequence), nil -} - -func NewPermissionsTxWithSequence(from acm.PublicKey, args snatives.PermArgs, sequence uint64) *PermissionsTx { - input := &TxInput{ - Address: from.Address(), - Amount: 1, // NOTE: amounts can't be 0 ... - Sequence: sequence, - PublicKey: from, - } - - return &PermissionsTx{ - Input: input, - PermArgs: args, - } -} - -func (tx *PermissionsTx) Sign(chainID string, privAccount acm.SigningAccount) { - tx.Input.PublicKey = privAccount.PublicKey() - tx.Input.Signature = acm.ChainSign(privAccount, chainID, tx) -} diff --git a/txs/unbond_tx.go b/txs/unbond_tx.go new file mode 100644 index 00000000..2411eaf9 --- /dev/null +++ b/txs/unbond_tx.go @@ -0,0 +1,46 @@ +package txs + +import ( + "fmt" + "io" + + acm "github.com/hyperledger/burrow/account" + "github.com/tendermint/go-wire" +) + +type UnbondTx struct { + Address acm.Address + Height int + Signature acm.Signature + txHashMemoizer +} + +var _ Tx = &UnbondTx{} + +func NewUnbondTx(addr acm.Address, height int) *UnbondTx { + return &UnbondTx{ + Address: addr, + Height: height, + } +} + +func (tx *UnbondTx) Sign(chainID string, privAccount acm.SigningAccount) { + tx.Signature = acm.ChainSign(privAccount, chainID, tx) +} + +func (tx *UnbondTx) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { + wire.WriteTo([]byte(fmt.Sprintf(`{"chain_id":%s`, jsonEscape(chainID))), w, n, err) + wire.WriteTo([]byte(fmt.Sprintf(`,"tx":[%v,{"address":"%s","height":%v}]}`, TxTypeUnbond, tx.Address, tx.Height)), w, n, err) +} + +func (tx *UnbondTx) GetInputs() []TxInput { + return nil +} + +func (tx *UnbondTx) String() string { + return fmt.Sprintf("UnbondTx{%s,%v,%v}", tx.Address, tx.Height, tx.Signature) +} + +func (tx *UnbondTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} -- GitLab