From 49fc1c6c1a3f44a959872a6ac8314c6055d88e34 Mon Sep 17 00:00:00 2001 From: Silas Davis <silas@monax.io> Date: Wed, 6 Jun 2018 23:29:54 +0100 Subject: [PATCH] Introduce transaction envelope model, drop go-wire Signed-off-by: Silas Davis <silas@monax.io> --- crypto/crypto.go | 20 +-- execution/events/events.go | 2 +- execution/execution.go | 1 + genesis/spec/genesis_spec.go | 107 ------------- genesis/spec/template_account.go | 116 ++++++++++++++ rpc/result.go | 4 +- rpc/result_test.go | 2 +- rpc/service.go | 2 +- rpc/tm/integration/client_test.go | 2 +- rpc/tm/methods.go | 2 +- txs/bond_tx.go | 49 +----- txs/call_tx.go | 30 +--- txs/gov_tx.go | 91 ----------- txs/governance_tx.go.txt | 77 +++++++++ txs/name_tx.go | 31 +--- txs/permission_tx.go | 30 +--- txs/rebond_tx.go | 56 ------- txs/send_tx.go | 46 +----- txs/tx.go | 253 +++++++++++++++++++++--------- txs/tx_input.go | 16 +- txs/tx_test.go | 57 +++---- txs/unbond_tx.go | 25 +-- 22 files changed, 435 insertions(+), 584 deletions(-) delete mode 100644 txs/gov_tx.go create mode 100644 txs/governance_tx.go.txt delete mode 100644 txs/rebond_tx.go diff --git a/crypto/crypto.go b/crypto/crypto.go index fc8ebf9e..80372257 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -1,9 +1,9 @@ package crypto import ( - "bytes" "fmt" - "io" + + "github.com/pkg/errors" ) type CurveType int8 @@ -57,15 +57,15 @@ type Signer interface { // Signable is an interface for all signable things. // It typically removes signatures before serializing. type Signable interface { - WriteSignBytes(chainID string, w io.Writer, n *int, err *error) + SignBytes(chainID string) ([]byte, error) } -// SignBytes is a convenience method for getting the bytes to sign of a Signable. -func SignBytes(chainID string, o Signable) []byte { - buf, n, err := new(bytes.Buffer), new(int), new(error) - o.WriteSignBytes(chainID, buf, n, err) - if *err != nil { - panic(fmt.Sprintf("could not write sign bytes for a signable: %s", *err)) +// SignBytes is a convenience method for getting the bytes to sign of a Signable. Will panic if there is an error +// generating SignBytes. +func SignBytes(chainID string, signable Signable) []byte { + bs, err := signable.SignBytes(chainID) + if err != nil { + panic(errors.Wrap(err, "could not write sign bytes for a signable")) } - return buf.Bytes() + return bs } diff --git a/execution/events/events.go b/execution/events/events.go index b9aa92df..d983df7a 100644 --- a/execution/events/events.go +++ b/execution/events/events.go @@ -37,7 +37,7 @@ var callTxQuery = event.NewQueryBuilder(). AndEquals(event.TxTypeKey, reflect.TypeOf(&txs.CallTx{}).String()) type eventDataTx struct { - Tx txs.Wrapper + Tx txs.Body Return []byte Exception string } diff --git a/execution/execution.go b/execution/execution.go index 3e7068f2..3c67293d 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -168,6 +168,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { debug.Stack()) } }() + body = txs.ChainWrap(exe.chainID, tx) txHash := tx.Hash(exe.chainID) logger := exe.logger.WithScope("executor.Execute(tx txs.Tx)").With( diff --git a/genesis/spec/genesis_spec.go b/genesis/spec/genesis_spec.go index 48d8443a..b1f6e920 100644 --- a/genesis/spec/genesis_spec.go +++ b/genesis/spec/genesis_spec.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/keys" "github.com/hyperledger/burrow/permission" @@ -31,112 +30,6 @@ type GenesisSpec struct { Accounts []TemplateAccount `json:",omitempty" toml:",omitempty"` } -type TemplateAccount struct { - // Template accounts sharing a name will be merged when merging genesis specs - Name string `json:",omitempty" toml:",omitempty"` - // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey - Address *crypto.Address `json:",omitempty" toml:",omitempty"` - PublicKey *crypto.PublicKey `json:",omitempty" toml:",omitempty"` - Amount *uint64 `json:",omitempty" toml:",omitempty"` - AmountBonded *uint64 `json:",omitempty" toml:",omitempty"` - Permissions []string `json:",omitempty" toml:",omitempty"` - Roles []string `json:",omitempty" toml:",omitempty"` -} - -func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) { - var err error - gv := new(genesis.Validator) - gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient) - if err != nil { - return nil, err - } - if ta.AmountBonded == nil { - gv.Amount = DefaultAmountBonded - } else { - gv.Amount = *ta.AmountBonded - } - if ta.Name == "" { - gv.Name = accountNameFromIndex(index) - } else { - gv.Name = ta.Name - } - - gv.UnbondTo = []genesis.BasicAccount{{ - Address: gv.Address, - PublicKey: gv.PublicKey, - Amount: gv.Amount, - }} - return gv, nil -} - -func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) { - basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions) - if err != nil { - return permission.ZeroAccountPermissions, nil - } - return ptypes.AccountPermissions{ - Base: basePerms, - Roles: ta.Roles, - }, nil -} - -func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) { - var err error - ga := new(genesis.Account) - ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient) - if err != nil { - return nil, err - } - if ta.Amount == nil { - ga.Amount = DefaultAmount - } else { - ga.Amount = *ta.Amount - } - if ta.Name == "" { - ga.Name = accountNameFromIndex(index) - } else { - ga.Name = ta.Name - } - if ta.Permissions == nil { - ga.Permissions = permission.DefaultAccountPermissions.Clone() - } else { - ga.Permissions, err = ta.AccountPermissions() - if err != nil { - return nil, err - } - } - return ga, nil -} - -// Adds a public key and address to the template. If PublicKey will try to fetch it by Address. -// If both PublicKey and Address are not set will use the keyClient to generate a new keypair -func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey crypto.PublicKey, address crypto.Address, err error) { - if ta.PublicKey == nil { - if ta.Address == nil { - // If neither PublicKey or Address set then generate a new one - address, err = keyClient.Generate(ta.Name, crypto.CurveTypeEd25519) - if err != nil { - return - } - } else { - address = *ta.Address - } - // Get the (possibly existing) key - pubKey, err = keyClient.PublicKey(address) - if err != nil { - return - } - } else { - address = (*ta.PublicKey).Address() - if ta.Address != nil && *ta.Address != address { - err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address, - ta.PublicKey) - } - pubKey = *ta.PublicKey - } - return -} - func (gs *GenesisSpec) RealiseKeys(keyClient keys.KeyClient) error { for _, templateAccount := range gs.Accounts { _, _, err := templateAccount.RealisePubKeyAndAddress(keyClient) diff --git a/genesis/spec/template_account.go b/genesis/spec/template_account.go index f09cd57a..51e43049 100644 --- a/genesis/spec/template_account.go +++ b/genesis/spec/template_account.go @@ -1 +1,117 @@ package spec + +import ( + "fmt" + + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +type TemplateAccount struct { + // Template accounts sharing a name will be merged when merging genesis specs + Name string `json:",omitempty" toml:",omitempty"` + // Address is convenient to have in file for reference, but otherwise ignored since derived from PublicKey + Address *crypto.Address `json:",omitempty" toml:",omitempty"` + PublicKey *crypto.PublicKey `json:",omitempty" toml:",omitempty"` + Amount *uint64 `json:",omitempty" toml:",omitempty"` + AmountBonded *uint64 `json:",omitempty" toml:",omitempty"` + Permissions []string `json:",omitempty" toml:",omitempty"` + Roles []string `json:",omitempty" toml:",omitempty"` +} + +func (ta TemplateAccount) Validator(keyClient keys.KeyClient, index int) (*genesis.Validator, error) { + var err error + gv := new(genesis.Validator) + gv.PublicKey, gv.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.AmountBonded == nil { + gv.Amount = DefaultAmountBonded + } else { + gv.Amount = *ta.AmountBonded + } + if ta.Name == "" { + gv.Name = accountNameFromIndex(index) + } else { + gv.Name = ta.Name + } + + gv.UnbondTo = []genesis.BasicAccount{{ + Address: gv.Address, + PublicKey: gv.PublicKey, + Amount: gv.Amount, + }} + return gv, nil +} + +func (ta TemplateAccount) AccountPermissions() (ptypes.AccountPermissions, error) { + basePerms, err := permission.BasePermissionsFromStringList(ta.Permissions) + if err != nil { + return permission.ZeroAccountPermissions, nil + } + return ptypes.AccountPermissions{ + Base: basePerms, + Roles: ta.Roles, + }, nil +} + +func (ta TemplateAccount) Account(keyClient keys.KeyClient, index int) (*genesis.Account, error) { + var err error + ga := new(genesis.Account) + ga.PublicKey, ga.Address, err = ta.RealisePubKeyAndAddress(keyClient) + if err != nil { + return nil, err + } + if ta.Amount == nil { + ga.Amount = DefaultAmount + } else { + ga.Amount = *ta.Amount + } + if ta.Name == "" { + ga.Name = accountNameFromIndex(index) + } else { + ga.Name = ta.Name + } + if ta.Permissions == nil { + ga.Permissions = permission.DefaultAccountPermissions.Clone() + } else { + ga.Permissions, err = ta.AccountPermissions() + if err != nil { + return nil, err + } + } + return ga, nil +} + +// Adds a public key and address to the template. If PublicKey will try to fetch it by Address. +// If both PublicKey and Address are not set will use the keyClient to generate a new keypair +func (ta TemplateAccount) RealisePubKeyAndAddress(keyClient keys.KeyClient) (pubKey crypto.PublicKey, address crypto.Address, err error) { + if ta.PublicKey == nil { + if ta.Address == nil { + // If neither PublicKey or Address set then generate a new one + address, err = keyClient.Generate(ta.Name, crypto.CurveTypeEd25519) + if err != nil { + return + } + } else { + address = *ta.Address + } + // Get the (possibly existing) key + pubKey, err = keyClient.PublicKey(address) + if err != nil { + return + } + } else { + address = (*ta.PublicKey).Address() + if ta.Address != nil && *ta.Address != address { + err = fmt.Errorf("template address %s does not match public key derived address %s", ta.Address, + ta.PublicKey) + } + pubKey = *ta.PublicKey + } + return +} diff --git a/rpc/result.go b/rpc/result.go index 63e05cf1..4879381a 100644 --- a/rpc/result.go +++ b/rpc/result.go @@ -201,7 +201,7 @@ func (rbt ResultBroadcastTx) UnmarshalJSON(data []byte) (err error) { type ResultListUnconfirmedTxs struct { NumTxs int - Txs []txs.Wrapper + Txs []txs.Body } type ResultGetName struct { @@ -213,7 +213,7 @@ type ResultGenesis struct { } type ResultSignTx struct { - Tx txs.Wrapper + Tx txs.Body } type TendermintEvent struct { diff --git a/rpc/result_test.go b/rpc/result_test.go index 2aa750ba..90312ddf 100644 --- a/rpc/result_test.go +++ b/rpc/result_test.go @@ -53,7 +53,7 @@ func TestResultBroadcastTx(t *testing.T) { func TestListUnconfirmedTxs(t *testing.T) { res := &ResultListUnconfirmedTxs{ NumTxs: 3, - Txs: []txs.Wrapper{ + Txs: []txs.Body{ txs.Wrap(&txs.CallTx{ Address: &crypto.Address{1}, }), diff --git a/rpc/service.go b/rpc/service.go index 0a360749..f50a874c 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -108,7 +108,7 @@ func (s *Service) ListUnconfirmedTxs(maxTxs int) (*ResultListUnconfirmedTxs, err if err != nil { return nil, err } - wrappedTxs := make([]txs.Wrapper, len(transactions)) + wrappedTxs := make([]txs.Body, len(transactions)) for i, tx := range transactions { wrappedTxs[i] = txs.Wrap(tx) } diff --git a/rpc/tm/integration/client_test.go b/rpc/tm/integration/client_test.go index 87bb496a..12c22e4e 100644 --- a/rpc/tm/integration/client_test.go +++ b/rpc/tm/integration/client_test.go @@ -323,7 +323,7 @@ func TestListUnconfirmedTxs(t *testing.T) { code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} // Call with nil address will create a contract tx := txs.Wrap(makeDefaultCallTx(t, client, nil, code, amt, gasLim, fee)) - txChan := make(chan []txs.Wrapper) + txChan := make(chan []txs.Body) // We want to catch the Tx in mempool before it gets reaped by tendermint // consensus. We should be able to do this almost always if we broadcast our diff --git a/rpc/tm/methods.go b/rpc/tm/methods.go index 987d4893..103109be 100644 --- a/rpc/tm/methods.go +++ b/rpc/tm/methods.go @@ -63,7 +63,7 @@ func GetRoutes(service *rpc.Service, logger *logging.Logger) map[string]*server. logger = logger.WithScope("GetRoutes") return map[string]*server.RPCFunc{ // Transact - BroadcastTx: server.NewRPCFunc(func(tx txs.Wrapper) (*rpc.ResultBroadcastTx, error) { + BroadcastTx: server.NewRPCFunc(func(tx txs.Body) (*rpc.ResultBroadcastTx, error) { receipt, err := service.Transactor().BroadcastTx(tx.Unwrap()) if err != nil { return nil, err diff --git a/txs/bond_tx.go b/txs/bond_tx.go index a7100ddf..f0a65555 100644 --- a/txs/bond_tx.go +++ b/txs/bond_tx.go @@ -2,12 +2,8 @@ package txs import ( "fmt" - "io" - - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" ) type BondTx struct { @@ -28,25 +24,8 @@ func NewBondTx(pubkey crypto.PublicKey) (*BondTx, error) { }, 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) Type() TxType { + return TxTypeBond } func (tx *BondTx) GetInputs() []TxInput { @@ -57,10 +36,6 @@ 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 crypto.PublicKey, amt uint64) error { addr := pubkey.Address() acc, err := st.GetAccount(addr) @@ -90,23 +65,3 @@ func (tx *BondTx) AddOutput(addr crypto.Address, amt uint64) error { }) return nil } - -func (tx *BondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != len(tx.Inputs)+1 { - return fmt.Errorf("BondTx expects %v SigningAccounts but got %v", len(tx.Inputs)+1, - len(signingAccounts)) - } - var err error - tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - for i := 1; i <= len(signingAccounts); i++ { - tx.Inputs[i].PublicKey = signingAccounts[i].PublicKey() - tx.Inputs[i].Signature, err = crypto.ChainSign(signingAccounts[i], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err) - } - } - return nil -} diff --git a/txs/call_tx.go b/txs/call_tx.go index ebfdb6f4..a1e593f0 100644 --- a/txs/call_tx.go +++ b/txs/call_tx.go @@ -2,12 +2,9 @@ package txs import ( "fmt" - "io" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" ) type CallTx struct { @@ -56,28 +53,9 @@ func NewCallTxWithSequence(from crypto.PublicKey, to *crypto.Address, data []byt } } -func (tx *CallTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded", - len(signingAccounts)) - } - var err error - tx.Input.PublicKey = signingAccounts[0].PublicKey() - tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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) Type() TxType { + return TxTypeCall } - func (tx *CallTx) GetInputs() []TxInput { return []TxInput{*tx.Input} } @@ -85,7 +63,3 @@ func (tx *CallTx) GetInputs() []TxInput { 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/gov_tx.go b/txs/gov_tx.go deleted file mode 100644 index ebfdb6f4..00000000 --- a/txs/gov_tx.go +++ /dev/null @@ -1,91 +0,0 @@ -package txs - -import ( - "fmt" - "io" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/account/state" - "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" -) - -type CallTx struct { - Input *TxInput - // Pointer since CallTx defines unset 'to' address as inducing account creation - Address *crypto.Address - GasLimit uint64 - Fee uint64 - Data []byte - txHashMemoizer -} - -var _ Tx = &CallTx{} - -func NewCallTx(st state.AccountGetter, from crypto.PublicKey, to *crypto.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 crypto.PublicKey, to *crypto.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, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("CallTx expects a single AddressableSigner for its single Input but %v were provieded", - len(signingAccounts)) - } - var err error - tx.Input.PublicKey = signingAccounts[0].PublicKey() - tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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/governance_tx.go.txt b/txs/governance_tx.go.txt new file mode 100644 index 00000000..b9da8ef2 --- /dev/null +++ b/txs/governance_tx.go.txt @@ -0,0 +1,77 @@ +package txs + +import ( + "fmt" + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/account/state" + "github.com/hyperledger/burrow/crypto" + "github.com/hyperledger/burrow/genesis/spec" + "bytes" +) + +type GovernanceTx struct { + GovTxPayload + txHashMemoizer +} + +type GovTxPayload struct { + AccountUpdates []spec.TemplateAccount +} + +var _ Tx = &GovernanceTx{} + +func NewGovTx(st state.AccountGetter, from crypto.PublicKey, accounts ...spec.TemplateAccount) (*GovernanceTx, 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 NewGovTxWithSequence(from, sequence, accounts), nil +} + +func NewGovTxWithSequence(from crypto.PublicKey, sequence uint64, accounts []spec.TemplateAccount) *GovernanceTx { + return &GovernanceTx{ + TxHeader: TxHeader{ + PublicKey: from, + Sequence: sequence, + }, + GovTxPayload: GovTxPayload{ + AccountUpdates: accounts, + }, + } +} + +func (tx *GovernanceTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { + if len(signingAccounts) != 1 { + return fmt.Errorf("GovernanceTx expects a single AddressableSigner for its single Input but %v were provieded", + len(signingAccounts)) + } + var err error + tx.Input.PublicKey = signingAccounts[0].PublicKey() + tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) + if err != nil { + return fmt.Errorf("could not sign %v: %v", tx, err) + } + return nil +} + +func (tx *GovernanceTx) SignBytes(chainID string) ([]byte, error) { + buf := new(bytes.Buffer) +} + +func (tx *GovernanceTx) GetInputs() []TxInput { + return []TxInput{*tx.Input} +} + +func (tx *GovernanceTx) String() string { + return fmt.Sprintf("GovernanceTx{%v -> %s: %X}", tx.Input, tx.Address, tx.Data) +} + +func (tx *GovernanceTx) Hash(chainID string) []byte { + return tx.txHashMemoizer.hash(chainID, tx) +} diff --git a/txs/name_tx.go b/txs/name_tx.go index d16ff0d1..479f7d46 100644 --- a/txs/name_tx.go +++ b/txs/name_tx.go @@ -2,14 +2,10 @@ package txs import ( "fmt" - "io" - "regexp" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" ) // Name should be file system lik @@ -57,27 +53,8 @@ func NewNameTxWithSequence(from crypto.PublicKey, name, data string, amt, fee, s } } -func (tx *NameTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("NameTx expects a single AddressableSigner for its single Input but %v were provieded", - len(signingAccounts)) - } - var err error - tx.Input.PublicKey = signingAccounts[0].PublicKey() - tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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) Type() TxType { + return TxTypeName } func (tx *NameTx) GetInputs() []TxInput { @@ -110,10 +87,6 @@ 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)) diff --git a/txs/permission_tx.go b/txs/permission_tx.go index 2ec99eb8..2bda5c5b 100644 --- a/txs/permission_tx.go +++ b/txs/permission_tx.go @@ -2,13 +2,10 @@ package txs import ( "fmt" - "io" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/crypto" "github.com/hyperledger/burrow/permission/snatives" - "github.com/tendermint/go-wire" ) type PermissionsTx struct { @@ -47,27 +44,8 @@ func NewPermissionsTxWithSequence(from crypto.PublicKey, args snatives.PermArgs, } } -func (tx *PermissionsTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("PermissionsTx expects a single AddressableSigner for its single Input but %v were provieded", - len(signingAccounts)) - } - var err error - tx.Input.PublicKey = signingAccounts[0].PublicKey() - tx.Input.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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) Type() TxType { + return TxTypePermissions } func (tx *PermissionsTx) GetInputs() []TxInput { @@ -77,7 +55,3 @@ func (tx *PermissionsTx) GetInputs() []TxInput { 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 deleted file mode 100644 index 8816937d..00000000 --- a/txs/rebond_tx.go +++ /dev/null @@ -1,56 +0,0 @@ -package txs - -import ( - "fmt" - "io" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" -) - -type RebondTx struct { - Address crypto.Address - Height int - Signature crypto.Signature - txHashMemoizer -} - -var _ Tx = &RebondTx{} - -func NewRebondTx(addr crypto.Address, height int) *RebondTx { - return &RebondTx{ - Address: addr, - Height: height, - } -} - -func (tx *RebondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("RebondTx expects a single AddressableSigner for its signature but %v were provieded", - len(signingAccounts)) - } - var err error - tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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 index 421d9f19..84735d97 100644 --- a/txs/send_tx.go +++ b/txs/send_tx.go @@ -2,12 +2,8 @@ package txs import ( "fmt" - "io" - - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/account/state" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" ) type SendTx struct { @@ -25,35 +21,16 @@ func NewSendTx() *SendTx { } } -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) Type() TxType { + return TxTypeSend } -func (tx *SendTx) Hash(chainID string) []byte { - return tx.txHashMemoizer.hash(chainID, tx) +func (tx *SendTx) String() string { + return fmt.Sprintf("SendTx{%v -> %v}", tx.Inputs, tx.Outputs) } func (tx *SendTx) AddInput(st state.AccountGetter, pubkey crypto.PublicKey, amt uint64) error { @@ -87,18 +64,3 @@ func (tx *SendTx) AddOutput(addr crypto.Address, amt uint64) error { return nil } -func (tx *SendTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != len(tx.Inputs) { - return fmt.Errorf("SendTx has %v Inputs but was provided with %v SigningAccounts", len(tx.Inputs), - len(signingAccounts)) - } - var err error - for i, signingAccount := range signingAccounts { - tx.Inputs[i].PublicKey = signingAccount.PublicKey() - tx.Inputs[i].Signature, err = crypto.ChainSign(signingAccount, chainID, tx) - if err != nil { - return fmt.Errorf("could not sign tx %v input %v: %v", tx, tx.Inputs[i], err) - } - } - return nil -} diff --git a/txs/tx.go b/txs/tx.go index e20292ef..f76261b1 100644 --- a/txs/tx.go +++ b/txs/tx.go @@ -18,11 +18,9 @@ import ( "encoding/json" "errors" "fmt" - "io" acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire/data" "golang.org/x/crypto/ripemd160" ) @@ -52,40 +50,50 @@ Admin Txs: - PermissionsTx */ +type TxType int8 + // Types of Tx implementations const ( + TxTypeUnknown = TxType(0x00) // Account transactions - TxTypeSend = byte(0x01) - TxTypeCall = byte(0x02) - TxTypeName = byte(0x03) + TxTypeSend = TxType(0x01) + TxTypeCall = TxType(0x02) + TxTypeName = TxType(0x03) // Validation transactions - TxTypeBond = byte(0x11) - TxTypeUnbond = byte(0x12) - TxTypeRebond = byte(0x13) + TxTypeBond = TxType(0x11) + TxTypeUnbond = TxType(0x12) // Admin transactions - TxTypePermissions = byte(0x1f) + TxTypePermissions = TxType(0x21) + TxTypeGovernance = TxType(0x22) ) -var mapper = data.NewMapper(Wrapper{}). - RegisterImplementation(&SendTx{}, "send_tx", TxTypeSend). - RegisterImplementation(&CallTx{}, "call_tx", TxTypeCall). - RegisterImplementation(&NameTx{}, "name_tx", TxTypeName). - RegisterImplementation(&BondTx{}, "bond_tx", TxTypeBond). - RegisterImplementation(&UnbondTx{}, "unbond_tx", TxTypeUnbond). - RegisterImplementation(&RebondTx{}, "rebond_tx", TxTypeRebond). - RegisterImplementation(&PermissionsTx{}, "permissions_tx", TxTypePermissions) +var txNameFromType = map[TxType]string{ + TxTypeUnknown: "UnknownTx", + TxTypeSend: "SendTx", + TxTypeCall: "CallTx", + TxTypeName: "NameTx", + TxTypeBond: "BondTx", + TxTypeUnbond: "UnbondTx", + TxTypePermissions: "PermissionsTx", + TxTypeGovernance: "GovernanceTx", +} + +var txTypeFromName = make(map[string]TxType) + +func init() { + for t, n := range txNameFromType { + txTypeFromName[n] = t + } +} - //----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- -// 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 - Sign(chainID string, signingAccounts ...acm.AddressableSigner) error + Type() TxType } type Encoder interface { @@ -96,6 +104,45 @@ type Decoder interface { DecodeTx(txBytes []byte) (Tx, error) } +func NewTx(txType TxType) Tx { + switch txType { + case TxTypeSend: + return &SendTx{} + case TxTypeCall: + return &CallTx{} + case TxTypeName: + return &NameTx{} + case TxTypeBond: + return &BondTx{} + case TxTypeUnbond: + return &UnbondTx{} + case TxTypePermissions: + return &PermissionsTx{} + } + return nil +} + +func (txType TxType) String() string { + name, ok := txNameFromType[txType] + if ok { + return name + } + return "UnknownTx" +} + +func TxTypeFromString(name string) TxType { + return txTypeFromName[name] +} + +func (txType TxType) MarshalText() ([]byte, error) { + return []byte(txType.String()), nil +} + +func (txType *TxType) UnmarshalText(data []byte) error { + *txType = TxTypeFromString(string(data)) + return nil +} + // BroadcastTx or Transact type Receipt struct { TxHash []byte @@ -103,69 +150,145 @@ type Receipt struct { ContractAddress crypto.Address } -type Wrapper struct { - Tx `json:"unwrap"` +type Envelope struct { + Signatures []crypto.Signature + Body } -// Wrap the Tx in a struct that allows for go-wire JSON serialisation -func Wrap(tx Tx) Wrapper { - if txWrapped, ok := tx.(Wrapper); ok { - return txWrapped +func (env *Envelope) Sign(signingAccounts ...acm.AddressableSigner) error { + signBytes, err := env.Body.SignBytes() + if err != nil { + return err } - return Wrapper{ - Tx: tx, + for _, sa := range signingAccounts { + sig, err := sa.Sign(signBytes) + if err != nil { + return err + } + env.Signatures = append(env.Signatures, sig) } + return nil } -// A serialisation wrapper that is itself a Tx -func (txw Wrapper) WriteSignBytes(chainID string, w io.Writer, n *int, err *error) { - txw.Tx.WriteSignBytes(chainID, w, n, err) +func SignTx(chainID string, tx Tx, signingAccounts ...acm.AddressableSigner) (*Envelope, error) { + env := &Envelope{ + Body: Body{ + ChainID: chainID, + Tx: tx, + }, + } + err := env.Sign(signingAccounts...) + if err != nil { + return nil, err + } + return env, nil } -func (txw Wrapper) MarshalJSON() ([]byte, error) { - return mapper.ToJSON(txw.Tx) +// The +type Body struct { + ChainID string + TxType TxType + Tx + txHash []byte } -func (txw *Wrapper) UnmarshalJSON(data []byte) (err error) { - parsed, err := mapper.FromJSON(data) - if err == nil && parsed != nil { - txw.Tx = parsed.(Tx) +// Wrap the Tx in Body required for signing and serialisation +func Wrap(tx Tx) *Body { + switch t := tx.(type) { + case Body: + return &t + case *Body: + return t + } + return &Body{ + TxType: tx.Type(), + Tx: tx, } - return err } -// Get the inner Tx that this Wrapper wraps -func (txw *Wrapper) Unwrap() Tx { - return txw.Tx +func ChainWrap(chainID string, tx Tx) *Body { + body := Wrap(tx) + body.ChainID = chainID + return body } -// Avoid re-hashing the same in-memory Tx -type txHashMemoizer struct { - txHashBytes []byte - chainID string +func (body *Body) MustSignBytes() []byte { + bs, err := body.SignBytes() + if err != nil { + panic(err) + } + return bs } -func (thm *txHashMemoizer) hash(chainID string, tx Tx) []byte { - if thm.txHashBytes == nil || thm.chainID != chainID { - thm.chainID = chainID - thm.txHashBytes = TxHash(chainID, tx) +func (body *Body) SignBytes() ([]byte, error) { + bs, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("could not generate canonical SignBytes for tx %v: %v", body.Tx, err) } - return thm.txHashBytes + return bs, nil +} + +// Serialisation intermediate for switching on type +type wrapper struct { + ChainID string + TxType TxType + Tx json.RawMessage } -func TxHash(chainID string, tx Tx) []byte { - signBytes := crypto.SignBytes(chainID, tx) +func (body Body) MarshalJSON() ([]byte, error) { + bs, err := json.Marshal(body.Tx) + if err != nil { + return nil, err + } + return json.Marshal(wrapper{ + ChainID: body.ChainID, + TxType: body.Type(), + Tx: bs, + }) +} + +func (body *Body) UnmarshalJSON(data []byte) error { + w := new(wrapper) + err := json.Unmarshal(data, w) + if err != nil { + return err + } + body.ChainID = w.ChainID + body.TxType = w.TxType + body.Tx = NewTx(w.TxType) + return json.Unmarshal(w.Tx, body.Tx) +} + +// Get the inner Tx that this Wrapper wraps +func (body *Body) Unwrap() Tx { + return body.Tx +} + +func (body *Body) Hash() []byte { + if body.txHash == nil { + return body.Rehash() + } + return body.txHash +} + +func (body *Body) Rehash() []byte { hasher := ripemd160.New() - hasher.Write(signBytes) - // Calling Sum(nil) just gives us the digest with nothing prefixed - return hasher.Sum(nil) + hasher.Write(body.MustSignBytes()) + body.txHash = hasher.Sum(nil) + return body.txHash +} + +// Avoid re-hashing the same in-memory Tx +type txHashMemoizer struct { + txHashBytes []byte + chainID string } -func GenerateReceipt(chainId string, tx Tx) Receipt { +func (body *Body) GenerateReceipt() Receipt { receipt := Receipt{ - TxHash: tx.Hash(chainId), + TxHash: body.Hash(), } - if callTx, ok := tx.(*CallTx); ok { + if callTx, ok := body.Tx.(*CallTx); ok { receipt.CreatesContract = callTx.Address == nil if receipt.CreatesContract { receipt.ContractAddress = crypto.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) @@ -202,13 +325,3 @@ func copyInputs(inputs []*TxInput) []TxInput { } return inputsCopy } - -// Contract: This function is deterministic and completely reversible. -func jsonEscape(str string) string { - // TODO: escape without panic - escapedBytes, err := json.Marshal(str) - if err != nil { - panic(fmt.Errorf("error json-escaping string: %s", str)) - } - return string(escapedBytes) -} diff --git a/txs/tx_input.go b/txs/tx_input.go index b8d4d4a9..610000c8 100644 --- a/txs/tx_input.go +++ b/txs/tx_input.go @@ -2,22 +2,20 @@ package txs import ( "fmt" - "io" - "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" + "bytes" ) type TxInput struct { Address crypto.Address + PublicKey crypto.PublicKey + Signature crypto.Signature Amount uint64 Sequence uint64 - Signature crypto.Signature - PublicKey crypto.PublicKey } func (txIn *TxInput) ValidateBasic() error { - if len(txIn.Address) != 20 { + if txIn.Address == crypto.ZeroAddress { return ErrTxInvalidAddress } if txIn.Amount == 0 { @@ -26,8 +24,10 @@ func (txIn *TxInput) ValidateBasic() error { 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) SignBytes() ([]byte, error) { + buf := new(bytes.Buffer) + buf.WriteString(fmt.Sprintf(`{"address":"%s","amount":%v,"sequence":%v}`, txIn.Address, txIn.Amount, txIn.Sequence)) + return buf.Bytes(), nil } func (txIn *TxInput) String() string { diff --git a/txs/tx_test.go b/txs/tx_test.go index 44f9892a..0be9effb 100644 --- a/txs/tx_test.go +++ b/txs/tx_test.go @@ -60,7 +60,7 @@ func TestSendTxSignable(t *testing.T) { }, }, } - signBytes := crypto.SignBytes(chainID, sendTx) + signBytes := ChainWrap(chainID, sendTx).MustSignBytes() signStr := string(signBytes) expected := fmt.Sprintf(`{"chain_id":"%s","tx":[1,{"inputs":[{"address":"%s","amount":12345,"sequence":67890},{"address":"%s","amount":111,"sequence":222}],"outputs":[{"address":"%s","amount":333},{"address":"%s","amount":444}]}]}`, chainID, sendTx.Inputs[0].Address.String(), sendTx.Inputs[1].Address.String(), sendTx.Outputs[0].Address.String(), sendTx.Outputs[1].Address.String()) @@ -83,7 +83,7 @@ func TestCallTxSignable(t *testing.T) { Fee: 222, Data: []byte("data1"), } - signBytes := crypto.SignBytes(chainID, callTx) + signBytes := ChainWrap(chainID, callTx).MustSignBytes() signStr := string(signBytes) expected := fmt.Sprintf(`{"chain_id":"%s","tx":[2,{"address":"%s","data":"6461746131","fee":222,"gas_limit":111,"input":{"address":"%s","amount":12345,"sequence":67890}}]}`, chainID, callTx.Address.String(), callTx.Input.Address.String()) @@ -103,7 +103,7 @@ func TestNameTxSignable(t *testing.T) { Data: "secretly.not.google.com", Fee: 1000, } - signBytes := crypto.SignBytes(chainID, nameTx) + signBytes := ChainWrap(chainID, nameTx).MustSignBytes() signStr := string(signBytes) expected := fmt.Sprintf(`{"chain_id":"%s","tx":[3,{"data":"secretly.not.google.com","fee":1000,"input":{"address":"%s","amount":12345,"sequence":250},"name":"google.com"}]}`, chainID, nameTx.Input.Address.String()) @@ -153,7 +153,8 @@ func TestBondTxSignable(t *testing.T) { bondTx.UnbondTo[0].Address.String(), bondTx.UnbondTo[1].Address.String()) - assert.Equal(t, expected, string(crypto.SignBytes(chainID, bondTx)), "Unexpected sign string for BondTx") + signBytes := ChainWrap(chainID, bondTx).MustSignBytes() + assert.Equal(t, expected, string(signBytes), "Unexpected sign string for BondTx") } func TestUnbondTxSignable(t *testing.T) { @@ -161,7 +162,7 @@ func TestUnbondTxSignable(t *testing.T) { Address: makeAddress("address1"), Height: 111, } - signBytes := crypto.SignBytes(chainID, unbondTx) + signBytes := ChainWrap(chainID, unbondTx).MustSignBytes() signStr := string(signBytes) expected := fmt.Sprintf(`{"chain_id":"%s","tx":[18,{"address":"%s","height":111}]}`, chainID, unbondTx.Address.String()) @@ -170,20 +171,6 @@ func TestUnbondTxSignable(t *testing.T) { } } -func TestRebondTxSignable(t *testing.T) { - rebondTx := &RebondTx{ - Address: makeAddress("address1"), - Height: 111, - } - signBytes := crypto.SignBytes(chainID, rebondTx) - signStr := string(signBytes) - expected := fmt.Sprintf(`{"chain_id":"%s","tx":[19,{"address":"%s","height":111}]}`, - chainID, rebondTx.Address.String()) - if signStr != expected { - t.Errorf("Got unexpected sign string for RebondTx") - } -} - func TestPermissionsTxSignable(t *testing.T) { permsTx := &PermissionsTx{ Input: &TxInput{ @@ -194,13 +181,18 @@ func TestPermissionsTxSignable(t *testing.T) { PermArgs: snatives.SetBaseArgs(makeAddress("address1"), 1, true), } - signBytes := crypto.SignBytes(chainID, permsTx) - signStr := string(signBytes) - expected := fmt.Sprintf(`{"chain_id":"%s","tx":[31,{"args":"{"PermFlag":%v,"Address":"%s","Permission":1,"Value":true}","input":{"address":"%s","amount":12345,"sequence":250}}]}`, - chainID, ptypes.SetBase, permsTx.PermArgs.Address.String(), permsTx.Input.Address.String()) - if signStr != expected { - t.Errorf("Got unexpected sign string for PermsTx. Expected:\n%v\nGot:\n%v", expected, signStr) + body := ChainWrap(chainID, permsTx) + env := Envelope{ + Body: *body, } + signBytes := body.MustSignBytes() + bodyOut := new(Body) + + bs, err := json.MarshalIndent(env, "", " ") + require.NoError(t, err) + fmt.Println(string(bs)) + require.NoError(t, json.Unmarshal(signBytes, bodyOut)) + assert.Equal(t, signBytes, body.MustSignBytes()) } func TestTxWrapper_MarshalJSON(t *testing.T) { @@ -228,24 +220,13 @@ func TestNewPermissionsTxWithSequence(t *testing.T) { } func testTxMarshalJSON(t *testing.T, tx Tx) { - txw := &Wrapper{Tx: tx} + txw := &Body{Tx: tx} bs, err := json.Marshal(txw) require.NoError(t, err) - txwOut := new(Wrapper) + txwOut := new(Body) err = json.Unmarshal(bs, txwOut) require.NoError(t, err) bsOut, err := json.Marshal(txwOut) require.NoError(t, err) assert.Equal(t, string(bs), string(bsOut)) } - -func TestTxHashMemoizer(t *testing.T) { - tx := &CallTx{ - Input: &TxInput{ - Sequence: 4, - }, - } - hsh := tx.Hash("foo") - assert.Equal(t, hsh, tx.txHashMemoizer.txHashBytes) - assert.Equal(t, "foo", tx.txHashMemoizer.chainID) -} diff --git a/txs/unbond_tx.go b/txs/unbond_tx.go index 850de4f5..4b77ced0 100644 --- a/txs/unbond_tx.go +++ b/txs/unbond_tx.go @@ -2,11 +2,8 @@ package txs import ( "fmt" - "io" - acm "github.com/hyperledger/burrow/account" "github.com/hyperledger/burrow/crypto" - "github.com/tendermint/go-wire" ) type UnbondTx struct { @@ -25,22 +22,8 @@ func NewUnbondTx(addr crypto.Address, height int) *UnbondTx { } } -func (tx *UnbondTx) Sign(chainID string, signingAccounts ...acm.AddressableSigner) error { - if len(signingAccounts) != 1 { - return fmt.Errorf("UnbondTx expects a single AddressableSigner for its signature but %v were provided", - len(signingAccounts)) - } - var err error - tx.Signature, err = crypto.ChainSign(signingAccounts[0], chainID, tx) - if err != nil { - return fmt.Errorf("could not sign %v: %v", tx, err) - } - return nil -} - -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) Type() TxType { + return TxTypeUnbond } func (tx *UnbondTx) GetInputs() []TxInput { @@ -50,7 +33,3 @@ func (tx *UnbondTx) GetInputs() []TxInput { 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