Skip to content

Commit

Permalink
txnbuild: enables multiple signatures (#1198)
Browse files Browse the repository at this point in the history
This PR enables multiple signatures on a transaction in the new Go SDK. It also lets every `Operation` type have a different source account than its `Transaction`. These changes are intertwined. Without multiple signatures, every operation in a transaction must share the transaction's source account. Differing source accounts are the most common use case for multiple signatures, and they also test it with greatest completeness.
  • Loading branch information
debnil authored Apr 30, 2019
1 parent 4616ed0 commit 47ee6ef
Show file tree
Hide file tree
Showing 19 changed files with 581 additions and 114 deletions.
11 changes: 8 additions & 3 deletions txnbuild/account_merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
// AccountMerge represents the Stellar merge account operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type AccountMerge struct {
Destination string
Destination string
SourceAccount Account
}

// BuildXDR for AccountMerge returns a fully configured XDR Operation.
Expand All @@ -22,6 +23,10 @@ func (am *AccountMerge) BuildXDR() (xdr.Operation, error) {

opType := xdr.OperationTypeAccountMerge
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, am.SourceAccount)
return op, nil
}
15 changes: 10 additions & 5 deletions txnbuild/allow_trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
// AllowTrust represents the Stellar allow trust operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type AllowTrust struct {
Trustor string
Type Asset
Authorize bool
Trustor string
Type Asset
Authorize bool
SourceAccount Account
}

// BuildXDR for AllowTrust returns a fully configured XDR Operation.
Expand Down Expand Up @@ -44,6 +45,10 @@ func (at *AllowTrust) BuildXDR() (xdr.Operation, error) {

opType := xdr.OperationTypeAllowTrust
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, at.SourceAccount)
return op, nil
}
11 changes: 8 additions & 3 deletions txnbuild/bump_sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import (
// BumpSequence represents the Stellar bump sequence operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type BumpSequence struct {
BumpTo int64
BumpTo int64
SourceAccount Account
}

// BuildXDR for BumpSequence returns a fully configured XDR Operation.
func (bs *BumpSequence) BuildXDR() (xdr.Operation, error) {
opType := xdr.OperationTypeBumpSequence
xdrOp := xdr.BumpSequenceOp{BumpTo: xdr.SequenceNumber(bs.BumpTo)}
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, bs.SourceAccount)
return op, nil
}
13 changes: 9 additions & 4 deletions txnbuild/change_trust.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
// ChangeTrust represents the Stellar change trust operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type ChangeTrust struct {
Line Asset
Limit string
Line Asset
Limit string
SourceAccount Account
}

// RemoveTrustlineOp returns a ChangeTrust operation to remove the trustline of the described asset,
Expand Down Expand Up @@ -43,6 +44,10 @@ func (ct *ChangeTrust) BuildXDR() (xdr.Operation, error) {
Limit: xdrLimit,
}
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, ct.SourceAccount)
return xdr.Operation{Body: body}, nil
}
5 changes: 4 additions & 1 deletion txnbuild/cmd/demo/operations/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,10 @@ func deleteTrustline(source *hProtocol.Account, asset txnbuild.Asset, signer Acc
}

func deleteOffer(source *hProtocol.Account, offerID int64, signer Account) (string, error) {
deleteOffer := txnbuild.DeleteOfferOp(offerID)
deleteOffer, err := txnbuild.DeleteOfferOp(offerID)
if err != nil {
return "", errors.Wrap(err, "building offer")
}

tx := txnbuild.Transaction{
SourceAccount: source,
Expand Down
13 changes: 9 additions & 4 deletions txnbuild/create_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
// CreateAccount represents the Stellar create account operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type CreateAccount struct {
Destination string
Amount string
Destination string
Amount string
SourceAccount Account
}

// BuildXDR for CreateAccount returns a fully configured XDR Operation.
Expand All @@ -29,6 +30,10 @@ func (ca *CreateAccount) BuildXDR() (xdr.Operation, error) {

opType := xdr.OperationTypeCreateAccount
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, ca.SourceAccount)
return op, nil
}
17 changes: 11 additions & 6 deletions txnbuild/create_passive_offer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
// CreatePassiveSellOffer represents the Stellar create passive offer operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type CreatePassiveSellOffer struct {
Selling Asset
Buying Asset
Amount string
Price string
Selling Asset
Buying Asset
Amount string
Price string
SourceAccount Account
}

// BuildXDR for CreatePassiveSellOffer returns a fully configured XDR Operation.
Expand Down Expand Up @@ -47,6 +48,10 @@ func (cpo *CreatePassiveSellOffer) BuildXDR() (xdr.Operation, error) {

opType := xdr.OperationTypeCreatePassiveSellOffer
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, cpo.SourceAccount)
return op, nil
}
9 changes: 6 additions & 3 deletions txnbuild/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,8 @@ func ExampleManageSellOffer() {
buying := CreditAsset{"ABCD", "GAS4V4O2B7DW5T7IQRPEEVCRXMDZESKISR7DVIGKZQYYV3OSQ5SH5LVP"}
sellAmount := "100"
price := "0.01"
op := CreateOfferOp(selling, buying, sellAmount, price)
op, err := CreateOfferOp(selling, buying, sellAmount, price)
check(err)

tx := Transaction{
SourceAccount: &sourceAccount,
Expand All @@ -329,7 +330,8 @@ func ExampleManageSellOffer_deleteOffer() {
check(err)

offerID := int64(2921622)
op := DeleteOfferOp(offerID)
op, err := DeleteOfferOp(offerID)
check(err)

tx := Transaction{
SourceAccount: &sourceAccount,
Expand Down Expand Up @@ -357,7 +359,8 @@ func ExampleManageSellOffer_updateOffer() {
sellAmount := "50"
price := "0.02"
offerID := int64(2497628)
op := UpdateOfferOp(selling, buying, sellAmount, price, offerID)
op, err := UpdateOfferOp(selling, buying, sellAmount, price, offerID)
check(err)

tx := Transaction{
SourceAccount: &sourceAccount,
Expand Down
14 changes: 5 additions & 9 deletions txnbuild/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,14 @@ func newKeypair(seed string) *keypair.Full {
return myKeypair.(*keypair.Full)
}

func buildSignEncode(tx Transaction, kp *keypair.Full, t *testing.T) (txeBase64 string) {
var err error
err = tx.Build()
assert.NoError(t, err)

err = tx.Sign(kp)
assert.NoError(t, err)
func buildSignEncode(t *testing.T, tx Transaction, kps ...*keypair.Full) string {
assert.NoError(t, tx.Build())
assert.NoError(t, tx.Sign(kps...))

txeBase64, err = tx.Base64()
txeBase64, err := tx.Base64()
assert.NoError(t, err)

return
return txeBase64
}

func check(err error) {
Expand Down
12 changes: 9 additions & 3 deletions txnbuild/inflation.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import (

// Inflation represents the Stellar inflation operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type Inflation struct{}
type Inflation struct {
SourceAccount Account
}

// BuildXDR for Inflation returns a fully configured XDR Operation.
func (inf *Inflation) BuildXDR() (xdr.Operation, error) {
opType := xdr.OperationTypeInflation
body, err := xdr.NewOperationBody(opType, nil)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, inf.SourceAccount)
return op, nil
}
13 changes: 9 additions & 4 deletions txnbuild/manage_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
// ManageData represents the Stellar manage data operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type ManageData struct {
Name string
Value []byte
Name string
Value []byte
SourceAccount Account
}

// BuildXDR for ManageData returns a fully configured XDR Operation.
Expand All @@ -26,6 +27,10 @@ func (md *ManageData) BuildXDR() (xdr.Operation, error) {

opType := xdr.OperationTypeManageData
body, err := xdr.NewOperationBody(opType, xdrOp)

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, md.SourceAccount)
return op, nil
}
61 changes: 46 additions & 15 deletions txnbuild/manage_offer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,78 @@ import (
)

//CreateOfferOp returns a ManageSellOffer operation to create a new offer, by
// setting the OfferID to "0".
func CreateOfferOp(selling, buying Asset, amount, price string) ManageSellOffer {
return ManageSellOffer{
// setting the OfferID to "0". The sourceAccount is optional, and if not provided,
// will be that of the surrounding transaction.
func CreateOfferOp(selling, buying Asset, amount, price string, sourceAccount ...Account) (ManageSellOffer, error) {
if len(sourceAccount) > 1 {
return ManageSellOffer{}, errors.New("offer can't have multiple source accounts")
}
offer := ManageSellOffer{
Selling: selling,
Buying: buying,
Amount: amount,
Price: price,
OfferID: 0,
}
if len(sourceAccount) == 1 {
offer.SourceAccount = sourceAccount[0]
}
return offer, nil
}

//UpdateOfferOp returns a ManageSellOffer operation to update an offer.
func UpdateOfferOp(selling, buying Asset, amount, price string, offerID int64) ManageSellOffer {
return ManageSellOffer{
// UpdateOfferOp returns a ManageSellOffer operation to update an offer.
// The sourceAccount is optional, and if not provided, will be that of
// the surrounding transaction.
func UpdateOfferOp(selling, buying Asset, amount, price string, offerID int64, sourceAccount ...Account) (ManageSellOffer, error) {
if len(sourceAccount) > 1 {
return ManageSellOffer{}, errors.New("offer can't have multiple source accounts")
}
offer := ManageSellOffer{
Selling: selling,
Buying: buying,
Amount: amount,
Price: price,
OfferID: offerID,
}
if len(sourceAccount) == 1 {
offer.SourceAccount = sourceAccount[0]
}
return offer, nil
}

//DeleteOfferOp returns a ManageSellOffer operation to delete an offer, by
// setting the Amount to "0".
func DeleteOfferOp(offerID int64) ManageSellOffer {
// setting the Amount to "0". The sourceAccount is optional, and if not provided,
// will be that of the surrounding transaction.
func DeleteOfferOp(offerID int64, sourceAccount ...Account) (ManageSellOffer, error) {
// It turns out Stellar core doesn't care about any of these fields except the amount.
// However, Horizon will reject ManageSellOffer if it is missing fields.
// Horizon will also reject if Buying == Selling.
// Therefore unfortunately we have to make up some dummy values here.
return ManageSellOffer{
if len(sourceAccount) > 1 {
return ManageSellOffer{}, errors.New("offer can't have multiple source accounts")
}
offer := ManageSellOffer{
Selling: NativeAsset{},
Buying: CreditAsset{Code: "FAKE", Issuer: "GBAQPADEYSKYMYXTMASBUIS5JI3LMOAWSTM2CHGDBJ3QDDPNCSO3DVAA"},
Amount: "0",
Price: "1",
OfferID: offerID,
}
if len(sourceAccount) == 1 {
offer.SourceAccount = sourceAccount[0]
}
return offer, nil
}

// ManageSellOffer represents the Stellar manage offer operation. See
// https://www.stellar.org/developers/guides/concepts/list-of-operations.html
type ManageSellOffer struct {
Selling Asset
Buying Asset
Amount string
Price string
OfferID int64
Selling Asset
Buying Asset
Amount string
Price string
OfferID int64
SourceAccount Account
}

// BuildXDR for ManageSellOffer returns a fully configured XDR Operation.
Expand Down Expand Up @@ -87,6 +113,11 @@ func (mo *ManageSellOffer) BuildXDR() (xdr.Operation, error) {
OfferId: xdr.Int64(mo.OfferID),
}
body, err := xdr.NewOperationBody(opType, xdrOp)
if err != nil {
return xdr.Operation{}, errors.Wrap(err, "failed to build XDR OperationBody")
}

return xdr.Operation{Body: body}, errors.Wrap(err, "failed to build XDR OperationBody")
op := xdr.Operation{Body: body}
SetOpSourceAccount(&op, mo.SourceAccount)
return op, nil
}
10 changes: 10 additions & 0 deletions txnbuild/operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ import (
type Operation interface {
BuildXDR() (xdr.Operation, error)
}

// SetOpSourceAccount sets the source account ID on an Operation.
func SetOpSourceAccount(op *xdr.Operation, sourceAccount Account) {
if sourceAccount == nil {
return
}
var opSourceAccountID xdr.AccountId
opSourceAccountID.SetAddress(sourceAccount.GetAccountID())
op.SourceAccount = &opSourceAccountID
}
Loading

0 comments on commit 47ee6ef

Please sign in to comment.