Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

txnbuild: Reinstate missing fee bump transaction functions #3199

Merged
merged 4 commits into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions txnbuild/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
All notable changes to this project will be documented in this
file. This project adheres to [Semantic Versioning](http://semver.org/).

## [v4.2.0](https://github.com/stellar/go/releases/tag/horizonclient-v4.2.0) - 2020-11-06

* Add `HashHex()`, `SignWithKeyString()`, `SignHashX()`, and `AddSignatureBase64()` functions back to `FeeBumpTransaction` ([#3199](https://github.com/stellar/go/pull/3199)).

## [v4.1.0](https://github.com/stellar/go/releases/tag/horizonclient-v4.1.0) - 2020-10-16

* Add helper function `ParseAssetString()`, making it easier to build an `Asset` structure from a string in [canonical form](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0011.md#asset) and check its various properties ([#3105](https://github.com/stellar/go/pull/3105)).
Expand Down
163 changes: 163 additions & 0 deletions txnbuild/fee_bump_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package txnbuild

import (
"crypto/sha256"
"encoding/base64"
"github.com/stellar/go/network"
"testing"
Expand Down Expand Up @@ -191,6 +192,161 @@ func TestFeeBumpAllowsFeeAccountToEqualInnerSourceAccount(t *testing.T) {
assert.NoError(t, err)
}

func TestFeeBumpSignWithKeyString(t *testing.T) {
kp0, kp1 := newKeypair0(), newKeypair1()
sourceAccount := NewSimpleAccount(kp0.Address(), 1)

tx, err := NewTransaction(
TransactionParams{
SourceAccount: &sourceAccount,
Operations: []Operation{&Inflation{}},
BaseFee: MinBaseFee,
Timebounds: NewInfiniteTimeout(),
},
)
assert.NoError(t, err)
tx, err = tx.Sign(network.TestNetworkPassphrase, kp0)
assert.NoError(t, err)

feeBumpTx, err := NewFeeBumpTransaction(
FeeBumpTransactionParams{
FeeAccount: kp1.Address(),
BaseFee: 2 * MinBaseFee,
Inner: tx,
},
)
assert.NoError(t, err)
feeBumpTx, err = feeBumpTx.Sign(network.TestNetworkPassphrase, kp1)
assert.NoError(t, err)
expectedBase64, err := feeBumpTx.Base64()
assert.NoError(t, err)

feeBumpTx, err = NewFeeBumpTransaction(
FeeBumpTransactionParams{
FeeAccount: kp1.Address(),
BaseFee: 2 * MinBaseFee,
marcelosalloum marked this conversation as resolved.
Show resolved Hide resolved
Inner: tx,
},
)
assert.NoError(t, err)
feeBumpTx, err = feeBumpTx.SignWithKeyString(network.TestNetworkPassphrase, kp1.Seed())
assert.NoError(t, err)
base64, err := feeBumpTx.Base64()
assert.NoError(t, err)

assert.Equal(t, expectedBase64, base64)
}

func TestFeeBumpSignHashX(t *testing.T) {
// 256 bit preimage
preimage := "this is a preimage for hashx transactions on the stellar network"
preimageHash := sha256.Sum256([]byte(preimage))

kp0, kp1 := newKeypair0(), newKeypair1()
payment := Payment{
Destination: "GCCOBXW2XQNUSL467IEILE6MMCNRR66SSVL4YQADUNYYNUVREF3FIV2Z",
Amount: "10",
Asset: NativeAsset{},
}
sourceAccount := NewSimpleAccount(kp0.Address(), int64(4353383146192899))

tx, err := NewTransaction(
TransactionParams{
SourceAccount: &sourceAccount,
IncrementSequenceNum: true,
Operations: []Operation{&payment},
BaseFee: MinBaseFee,
Timebounds: NewInfiniteTimeout(),
},
)
assert.NoError(t, err)
tx, err = tx.Sign(network.TestNetworkPassphrase, kp0)
assert.NoError(t, err)

feeBumpTx, err := NewFeeBumpTransaction(
FeeBumpTransactionParams{
FeeAccount: kp1.Address(),
BaseFee: 2 * MinBaseFee,
Inner: tx,
},
)
assert.NoError(t, err)
feeBumpTx, err = feeBumpTx.SignHashX([]byte(preimage))
assert.NoError(t, err)

signatures := feeBumpTx.Signatures()
assert.Len(t, signatures, 1)
assert.Equal(t, xdr.Signature(preimage), signatures[0].Signature)
var expectedHint [4]byte
copy(expectedHint[:], preimageHash[28:])
assert.Equal(t, xdr.SignatureHint(expectedHint), signatures[0].Hint)
}

func TestFeeBumpAddSignatureBase64(t *testing.T) {
kp0 := newKeypair0()
kp1 := newKeypair1()
kp2 := newKeypair2()
txSource := NewSimpleAccount(kp0.Address(), int64(9605939170639897))
opSource := NewSimpleAccount(kp1.Address(), 0)
createAccount := CreateAccount{
Destination: "GCCOBXW2XQNUSL467IEILE6MMCNRR66SSVL4YQADUNYYNUVREF3FIV2Z",
Amount: "10",
SourceAccount: &opSource,
}

inner, err := NewTransaction(
TransactionParams{
SourceAccount: &txSource,
IncrementSequenceNum: true,
Operations: []Operation{&createAccount},
BaseFee: MinBaseFee,
Timebounds: NewInfiniteTimeout(),
},
)
assert.NoError(t, err)
inner, err = inner.Sign(network.TestNetworkPassphrase, kp0)
assert.NoError(t, err)

tx, err := NewFeeBumpTransaction(
FeeBumpTransactionParams{
FeeAccount: kp1.Address(),
BaseFee: 2 * MinBaseFee,
Inner: inner,
},
)
assert.NoError(t, err)
tx, err = tx.Sign(network.TestNetworkPassphrase, kp1, kp2)
assert.NoError(t, err)
expected, err := tx.Base64()
assert.NoError(t, err)
signatures := tx.Signatures()

otherTx, err := NewFeeBumpTransaction(
FeeBumpTransactionParams{
FeeAccount: kp1.Address(),
BaseFee: 2 * MinBaseFee,
Inner: inner,
},
)
assert.NoError(t, err)
otherTx, err = otherTx.AddSignatureBase64(
network.TestNetworkPassphrase,
kp1.Address(),
base64.StdEncoding.EncodeToString(signatures[0].Signature),
)
assert.NoError(t, err)
otherTx, err = otherTx.AddSignatureBase64(
network.TestNetworkPassphrase,
kp2.Address(),
base64.StdEncoding.EncodeToString(signatures[1].Signature),
)
assert.NoError(t, err)
b64, err := tx.Base64()
assert.NoError(t, err)

assert.Equal(t, expected, b64)
}

func TestFeeBumpRoundTrip(t *testing.T) {
kp0, kp1 := newKeypair0(), newKeypair1()
sourceAccount := NewSimpleAccount(kp0.Address(), 1)
Expand Down Expand Up @@ -228,6 +384,9 @@ func TestFeeBumpRoundTrip(t *testing.T) {
assert.Equal(t, int64(2*MinBaseFee), feeBumpTx.BaseFee())
assert.Equal(t, int64(4*MinBaseFee), feeBumpTx.MaxFee())

outerHash, err := feeBumpTx.HashHex(network.TestNetworkPassphrase)
assert.NoError(t, err)

env, err := feeBumpTx.TxEnvelope()
assert.NoError(t, err)
assert.Equal(t, xdr.EnvelopeTypeEnvelopeTypeTxFeeBump, env.Type)
Expand Down Expand Up @@ -259,6 +418,9 @@ func TestFeeBumpRoundTrip(t *testing.T) {
_, ok = parsed.Transaction()
assert.False(t, ok)

parsedHash, err := parsedFeeBump.HashHex(network.TestNetworkPassphrase)
assert.NoError(t, err)

assert.Equal(t, feeBumpTx.Signatures(), parsedFeeBump.Signatures())
assert.Equal(t, kp1.Address(), parsedFeeBump.FeeAccount())
assert.Equal(t, int64(2*MinBaseFee), parsedFeeBump.BaseFee())
Expand All @@ -272,4 +434,5 @@ func TestFeeBumpRoundTrip(t *testing.T) {
b64, err = parsedFeeBump.Base64()
assert.NoError(t, err)
assert.Equal(t, expectedFeeBumpB64, b64)
assert.Equal(t, outerHash, parsedHash)
}
45 changes: 45 additions & 0 deletions txnbuild/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,12 @@ func (t *FeeBumpTransaction) Hash(networkStr string) ([32]byte, error) {
return network.HashTransactionInEnvelope(t.envelope, networkStr)
}

// HashHex returns the network specific hash of this transaction
// encoded as a hexadecimal string.
func (t *FeeBumpTransaction) HashHex(network string) (string, error) {
return hashHex(t.envelope, network)
}

// Sign returns a new FeeBumpTransaction instance which extends the current instance
// with additional signatures derived from the given list of keypair instances.
func (t *FeeBumpTransaction) Sign(network string, kps ...*keypair.Full) (*FeeBumpTransaction, error) {
Expand All @@ -454,6 +460,45 @@ func (t *FeeBumpTransaction) Sign(network string, kps ...*keypair.Full) (*FeeBum
return newTx, nil
}

// SignWithKeyString returns a new FeeBumpTransaction instance which extends the current instance
// with additional signatures derived from the given list of private key strings.
func (t *FeeBumpTransaction) SignWithKeyString(network string, keys ...string) (*FeeBumpTransaction, error) {
kps, err := stringsToKP(keys...)
if err != nil {
return nil, err
}
return t.Sign(network, kps...)
}

// SignHashX returns a new FeeBumpTransaction instance which extends the current instance
// with HashX signature type.
// See description here: https://www.stellar.org/developers/guides/concepts/multi-sig.html#hashx.
func (t *FeeBumpTransaction) SignHashX(preimage []byte) (*FeeBumpTransaction, error) {
extendedSignatures, err := concatHashX(t.signatures, preimage)
if err != nil {
return nil, err
}

newTx := new(FeeBumpTransaction)
*newTx = *t
newTx.signatures = extendedSignatures
return newTx, nil
}

// AddSignatureBase64 returns a new FeeBumpTransaction instance which extends the current instance
// with an additional signature derived from the given base64-encoded signature.
func (t *FeeBumpTransaction) AddSignatureBase64(network, publicKey, signature string) (*FeeBumpTransaction, error) {
extendedSignatures, err := concatSignatureBase64(t.envelope, t.signatures, network, publicKey, signature)
if err != nil {
return nil, err
}

newTx := new(FeeBumpTransaction)
*newTx = *t
newTx.signatures = extendedSignatures
Comment on lines +496 to +498
Copy link
Contributor

@Shaptic Shaptic Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you're just reinstating missing functionality, but can I ask what this is doing? Is this like a Go-idiomatic way to copy a struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in Go the assignment operator copies by value. However, the assignment operator does a shallow copy. If you have any pointers or slices in your structs, the new struct will still have references to those fields. Here's an article which provides more details on this topic https://flaviocopes.com/go-copying-structs/

In this particular case, the signatures field is the only mutable field in the struct. There are no exported functions on FeeBumpTransaction or Transaction which modify any of the other fields in the struct. The code creates a copy of the FeeBumpTransaction struct via assignment and then it does a deep copy on the signatures slice.

return newTx, nil
}

// TxEnvelope returns the a xdr.TransactionEnvelope instance which is
// equivalent to this transaction.
func (t *FeeBumpTransaction) TxEnvelope() (xdr.TransactionEnvelope, error) {
Expand Down