From 63e0e3f778868c9ed2bea4dbc4acb0483def3609 Mon Sep 17 00:00:00 2001 From: George Date: Wed, 7 Oct 2020 13:00:15 -0700 Subject: [PATCH] services/horizon: Improve sponsorship integration tests' running time & flexibility. (#3090) Refactors the sponsorship (CAP-33) integration tests: - Each test is independently runs as a sub-test of the "TestSponsorships" test. - It introduces helper functions commonly used in each tests such as sandwiching sponsored operations, retrieving operations and effects, and confirming the existence of operations via `tt.Condition`. - `itest.MustGetAccount()` replaces instances of a manual force-retrieval of an account, and account/keypair management in general is simpler. - Running time is ~3x faster. All of the actual _testing_ is still the same. Investigation on using `testing.T.Parallel` is in progress which should further improve running time. Closes #3075. --- .../protocol14_sponsorship_ops_test.go | 1507 +++++++---------- services/horizon/internal/test/integration.go | 5 +- 2 files changed, 639 insertions(+), 873 deletions(-) diff --git a/services/horizon/internal/integration/protocol14_sponsorship_ops_test.go b/services/horizon/internal/integration/protocol14_sponsorship_ops_test.go index ae9515a2e1..4d67d9dff2 100644 --- a/services/horizon/internal/integration/protocol14_sponsorship_ops_test.go +++ b/services/horizon/internal/integration/protocol14_sponsorship_ops_test.go @@ -2,8 +2,9 @@ package integration import ( "encoding/base64" + "fmt" + "strconv" "testing" - "time" "github.com/stretchr/testify/assert" @@ -17,680 +18,479 @@ import ( "github.com/stellar/go/xdr" ) -func TestSponsoredAccount(t *testing.T) { +func TestSponsorships(t *testing.T) { tt := assert.New(t) itest := test.NewIntegrationTest(t, protocol14Config) - sponsor := itest.MasterAccount - sponsorPair := itest.Master() + client := itest.Client() - // We will create the following operation structure: - // BeginSponsoringFutureReserves A - // CreateAccount A - // EndSponsoringFutureReserves (with A as a source) - - newAccountPair, err := keypair.Random() - tt.NoError(err) - - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SponsoredID: newAccountPair.Address(), - }, - - &txnbuild.CreateAccount{ - Destination: newAccountPair.Address(), - Amount: "1000", - }, - &txnbuild.EndSponsoringFutureReserves{ - SourceAccount: &txnbuild.SimpleAccount{ - AccountID: newAccountPair.Address(), - }, - }, - } - - signers := []*keypair.Full{sponsorPair, newAccountPair} - txResp, err := itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - response, err := itest.Client().Operations(sdk.OperationRequest{ - Order: "asc", - }) - opRecords := response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 3) - tt.True(opRecords[0].IsTransactionSuccessful()) + /* Query helpers that can/should? probably be added to IntegrationTest. */ - // Verify operation details - tt.Equal(ops[0].(*txnbuild.BeginSponsoringFutureReserves).SponsoredID, - opRecords[0].(operations.BeginSponsoringFutureReserves).SponsoredID) - - actualCreateAccount := opRecords[1].(operations.CreateAccount) - tt.Equal(sponsorPair.Address(), actualCreateAccount.Sponsor) - - endSponsoringOp := opRecords[2].(operations.EndSponsoringFutureReserves) - tt.Equal(sponsorPair.Address(), endSponsoringOp.BeginSponsor) - - // Make sure that the sponsor is an (implicit) participant on the end sponsorship operation - - response, err = itest.Client().Operations(sdk.OperationRequest{ - ForAccount: sponsorPair.Address(), - }) - tt.NoError(err) - - endSponsorshipPresent := func() bool { - for _, o := range response.Embedded.Records { - if o.GetID() == endSponsoringOp.ID { - return true - } - } - return false + getOperationsByTx := func(txHash string) []operations.Operation { + response, err := client.Operations(sdk.OperationRequest{ForTransaction: txHash}) + tt.NoError(err) + return response.Embedded.Records } - tt.Condition(endSponsorshipPresent) - - // Check numSponsoring and numSponsored - account, err := itest.Client().AccountDetail(sdk.AccountRequest{ - AccountID: sponsorPair.Address(), - }) - tt.NoError(err) - account.NumSponsoring = 1 - account, err = itest.Client().AccountDetail(sdk.AccountRequest{ - AccountID: newAccountPair.Address(), - }) - tt.NoError(err) - account.NumSponsored = 1 - - // Check effects of CreateAccount Operation - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ForOperation: opRecords[1].GetID()}) - tt.NoError(err) - effectRecords := effectsResponse.Embedded.Records - tt.Len(effectRecords, 4) - tt.IsType(effects.AccountSponsorshipCreated{}, effectRecords[3]) - tt.Equal(sponsorPair.Address(), effectRecords[3].(effects.AccountSponsorshipCreated).Sponsor) - - // Update sponsor - - accountToRevoke := newAccountPair.Address() - pairs, _ := itest.CreateAccounts(1, "1000") - newSponsorPair := pairs[0] - newSponsor := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newSponsorPair.Address()} - account, e := itest.Client().AccountDetail(request) - tt.NoError(e) - return &account - } - ops = []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: newSponsor(), - SponsoredID: sponsorPair.Address(), - }, - &txnbuild.RevokeSponsorship{ - SourceAccount: sponsor(), - SponsorshipType: txnbuild.RevokeSponsorshipTypeAccount, - Account: &accountToRevoke, - }, - &txnbuild.EndSponsoringFutureReserves{}, + getEffectsByOp := func(opId string) []effects.Effect { + response, err := client.Effects(sdk.EffectRequest{ForOperation: opId}) + tt.NoError(err) + return response.Embedded.Records } - signers = []*keypair.Full{sponsorPair, newSponsorPair} - txResp, err = itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - // Verify operation details - response, err = itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - opRecords = response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 3) - tt.True(opRecords[1].IsTransactionSuccessful()) - - revokeOp := opRecords[1].(operations.RevokeSponsorship) - tt.Equal(accountToRevoke, *revokeOp.AccountID) - - // Check effects - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords = effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.AccountSponsorshipUpdated{}, effectRecords[0]) - effect := effectRecords[0].(effects.AccountSponsorshipUpdated) - tt.Equal(sponsorPair.Address(), effect.FormerSponsor) - tt.Equal(newSponsorPair.Address(), effect.NewSponsor) - - // Revoke sponsorship - - op := &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeAccount, - Account: &accountToRevoke, + getEffectsByTx := func(txId string) []effects.Effect { + response, err := client.Effects(sdk.EffectRequest{ForTransaction: txId}) + tt.NoError(err) + return response.Embedded.Records } - txResp = itest.MustSubmitOperations(newSponsor(), newSponsorPair, op) - // Verify operation details - response, err = itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - opRecords = response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 1) - tt.True(opRecords[0].IsTransactionSuccessful()) - - revokeOp = opRecords[0].(operations.RevokeSponsorship) - tt.Equal(accountToRevoke, *revokeOp.AccountID) + /* + * Each test has its own sponsor and sponsoree (or is it sponsee? + * :thinking:) so that we can do direct equality checks. + * + * Each sub-test follows a similar structure: + * - sponsor a particular operation + * - replace the sponsor with a new one + * - revoke the sponsorship + * + * Between each step, we validate /operations, /effects, etc. according to + * the expected behavior for that sponsorship. + */ - // Make sure that the sponsoree is an (implicit) participant in the revocation operation - response, err = itest.Client().Operations(sdk.OperationRequest{ - ForAccount: newAccountPair.Address(), - }) - tt.NoError(err) + // We will create the following operation structure: + // BeginSponsoringFutureReserves A + // CreateAccount A + // EndSponsoringFutureReserves (with A as a source) + t.Run("CreateAccount", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(2, "1000") + sponsor, sponsorPair := accounts[0], keys[0] + newAccountKeys := keypair.MustRandom() + newAccountID := newAccountKeys.Address() + + t.Logf("Testing sponsorship of CreateAccount operation") + ops := sponsorOperations(newAccountID, + &txnbuild.CreateAccount{ + Destination: newAccountID, + Amount: "100", + }) - sponsorshipRevocationPresent := func() bool { - for _, o := range response.Embedded.Records { - if o.GetID() == revokeOp.ID { - return true - } + signers := []*keypair.Full{sponsorPair, newAccountKeys} + txResp, err := itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Ensure that the operations are in fact the droids we're looking for + opRecords := getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 3) + tt.True(opRecords[0].IsTransactionSuccessful()) + + startSponsoringOp := opRecords[0].(operations.BeginSponsoringFutureReserves) + actualCreateAccount := opRecords[1].(operations.CreateAccount) + endSponsoringOp := opRecords[2].(operations.EndSponsoringFutureReserves) + + tt.Equal(newAccountID, startSponsoringOp.SponsoredID) + tt.Equal(sponsorPair.Address(), actualCreateAccount.Sponsor) + tt.Equal(sponsorPair.Address(), endSponsoringOp.BeginSponsor) + + // Make sure that the sponsor is an (implicit) participant on the end + // sponsorship operation + response, err := client.Operations(sdk.OperationRequest{ForAccount: sponsorPair.Address()}) + tt.Condition(findOperationByID(endSponsoringOp.ID, response.Embedded.Records)) + t.Logf(" operations accurate") + + // Check that the num_sponsoring and num_sponsored fields are accurate + tt.EqualValues(2, itest.MustGetAccount(sponsorPair).NumSponsoring) + tt.EqualValues(2, itest.MustGetAccount(newAccountKeys).NumSponsored) + t.Logf(" accounts accurate") + + // Check effects of CreateAccount Operation + effectRecords := getEffectsByOp(actualCreateAccount.GetID()) + tt.Len(effectRecords, 4) + tt.Equal(sponsorPair.Address(), + effectRecords[3].(effects.AccountSponsorshipCreated).Sponsor) + t.Logf(" effects accurate") + + // Update sponsor + newSponsorPair, newSponsor := keys[1], accounts[1] + + t.Logf("Revoking & replacing sponsorship") + ops = []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{ + SourceAccount: newSponsor, + SponsoredID: sponsorPair.Address(), + }, + &txnbuild.RevokeSponsorship{ + SourceAccount: sponsor, + SponsorshipType: txnbuild.RevokeSponsorshipTypeAccount, + Account: &newAccountID, + }, + &txnbuild.EndSponsoringFutureReserves{}, } - return false - } - tt.Condition(sponsorshipRevocationPresent) - - // Check effects - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords = effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.AccountSponsorshipRemoved{}, effectRecords[0]) - tt.Equal(newSponsorPair.Address(), effectRecords[0].(effects.AccountSponsorshipRemoved).FormerSponsor) -} -func TestSponsoredSigner(t *testing.T) { - tt := assert.New(t) - itest := test.NewIntegrationTest(t, protocol14Config) - sponsorPair := itest.Master() - sponsor := itest.MasterAccount - - // Let's create a new account - pairs, _ := itest.CreateAccounts(1, "1000") - newAccountPair := pairs[0] - newAccount := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newAccountPair.Address()} - account, err := itest.Client().AccountDetail(request) + signers = []*keypair.Full{sponsorPair, newSponsorPair} + txResp, err = itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify operation details + response, err = client.Operations(sdk.OperationRequest{ + ForTransaction: txResp.Hash, + }) tt.NoError(err) - return &account - } + opRecords = response.Embedded.Records + tt.Len(opRecords, 3) + tt.True(opRecords[1].IsTransactionSuccessful()) + + revokeOp := opRecords[1].(operations.RevokeSponsorship) + tt.Equal(newAccountID, *revokeOp.AccountID) + t.Logf(" operations accurate") + + // Check effects + effectRecords = getEffectsByOp(revokeOp.ID) + tt.Len(effectRecords, 1) + effect := effectRecords[0].(effects.AccountSponsorshipUpdated) + tt.Equal(sponsorPair.Address(), effect.FormerSponsor) + tt.Equal(newSponsorPair.Address(), effect.NewSponsor) + t.Logf(" effects accurate") + + // Revoke sponsorship + + t.Logf("Revoking sponsorship entirely") + op := &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeAccount, + Account: &newAccountID, + } + txResp = itest.MustSubmitOperations(newSponsor, newSponsorPair, op) + + // Verify operation details + opRecords = getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 1) + tt.True(opRecords[0].IsTransactionSuccessful()) + revokeOp = opRecords[0].(operations.RevokeSponsorship) + tt.Equal(newAccountID, *revokeOp.AccountID) + + // Make sure that the sponsoree is an (implicit) participant in the + // revocation operation + response, err = client.Operations(sdk.OperationRequest{ForAccount: newAccountID}) + tt.Condition(findOperationByID(revokeOp.ID, response.Embedded.Records)) + t.Logf(" operations accurate") + + // Check effects + effectRecords = getEffectsByOp(revokeOp.ID) + tt.Len(effectRecords, 1) + tt.IsType(effects.AccountSponsorshipRemoved{}, effectRecords[0]) + desponsorOp := effectRecords[0].(effects.AccountSponsorshipRemoved) + tt.Equal(newSponsorPair.Address(), desponsorOp.FormerSponsor) + t.Logf(" effects accurate") + }) // Let's add a sponsored data entry - // // BeginSponsorship N (Source=sponsor) // SetOptionsSigner (Source=N) // EndSponsorship (Source=N) - - // unspecific signer - signerKey := "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML" - - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SponsoredID: newAccountPair.Address(), - }, - &txnbuild.SetOptions{ - SourceAccount: newAccount(), + t.Run("Signer", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(3, "1000") + sponsorPair, sponsor := keys[0], accounts[0] + newAccountPair, newAccount := keys[1], accounts[1] + signerKey := keypair.MustRandom().Address() // unspecified signer + + ops := sponsorOperations(newAccountPair.Address(), &txnbuild.SetOptions{ + SourceAccount: newAccount, Signer: &txnbuild.Signer{ Address: signerKey, Weight: 1, }, - }, - &txnbuild.EndSponsoringFutureReserves{ - SourceAccount: newAccount(), - }, - } + }) - signers := []*keypair.Full{sponsorPair, newAccountPair} - txResp, err := itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify that the signer was incorporated - signerAdded := func() bool { - signers := newAccount().(*protocol.Account).Signers - for _, signer := range signers { - if signer.Key == signerKey { - tt.Equal(sponsorPair.Address(), signer.Sponsor) - return true + signers := []*keypair.Full{sponsorPair, newAccountPair} + txResp, err := itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify that the signer was incorporated + signerAdded := func() bool { + signers := itest.MustGetAccount(newAccountPair).Signers + for _, signer := range signers { + if signer.Key == signerKey { + tt.Equal(sponsorPair.Address(), signer.Sponsor) + return true + } } + return false } - return false - } - tt.Eventually(signerAdded, time.Second*10, time.Millisecond*100) + tt.Condition(signerAdded) - // Check effects and details of the SetOptions operation - operationsResponse, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - tt.Len(operationsResponse.Embedded.Records, 3) - setOptionsOp := operationsResponse.Embedded.Records[1].(operations.SetOptions) - tt.Equal(sponsorPair.Address(), setOptionsOp.Sponsor) + // Check effects and details of the SetOptions operation + opRecords := getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 3) + setOptionsOp := opRecords[1].(operations.SetOptions) + tt.Equal(sponsorPair.Address(), setOptionsOp.Sponsor) - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ - ForOperation: setOptionsOp.GetID(), - }) - tt.NoError(err) - if tt.Len(effectsResponse.Embedded.Records, 2) { - signerSponsorshipEffect := effectsResponse.Embedded.Records[1].(effects.SignerSponsorshipCreated) + effRecords := getEffectsByOp(setOptionsOp.GetID()) + tt.Len(effRecords, 2) + signerSponsorshipEffect := effRecords[1].(effects.SignerSponsorshipCreated) tt.Equal(sponsorPair.Address(), signerSponsorshipEffect.Sponsor) tt.Equal(newAccountPair.Address(), signerSponsorshipEffect.Account) tt.Equal(signerKey, signerSponsorshipEffect.Signer) - } - // Update sponsor + // Update sponsor + newSponsorPair, newSponsor := keys[2], accounts[2] + ops = []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{ + SourceAccount: newSponsor, + SponsoredID: sponsorPair.Address(), + }, + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeSigner, + Signer: &txnbuild.SignerID{ + AccountID: newAccountPair.Address(), + SignerAddress: signerKey, + }, + }, + &txnbuild.EndSponsoringFutureReserves{}, + } - pairs, _ = itest.CreateAccounts(1, "1000") - newSponsorPair := pairs[0] - newSponsor := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newSponsorPair.Address()} - account, e := itest.Client().AccountDetail(request) - tt.NoError(e) - return &account - } - ops = []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: newSponsor(), - SponsoredID: sponsorPair.Address(), - }, - &txnbuild.RevokeSponsorship{ + signers = []*keypair.Full{sponsorPair, newSponsorPair} + txResp, err = itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify operation details + opRecords = getOperationsByTx(txResp.Hash) + tt.NoError(err) + tt.Len(opRecords, 3) + tt.True(opRecords[1].IsTransactionSuccessful()) + + revokeOp := opRecords[1].(operations.RevokeSponsorship) + tt.Equal(newAccountPair.Address(), *revokeOp.SignerAccountID) + tt.Equal(signerKey, *revokeOp.SignerKey) + + // Check effects + effRecords = getEffectsByOp(revokeOp.ID) + tt.Len(effRecords, 1) + effect := effRecords[0].(effects.SignerSponsorshipUpdated) + tt.Equal(sponsorPair.Address(), effect.FormerSponsor) + tt.Equal(newSponsorPair.Address(), effect.NewSponsor) + tt.Equal(signerKey, effect.Signer) + + // Revoke sponsorship + + revoke := txnbuild.RevokeSponsorship{ SponsorshipType: txnbuild.RevokeSponsorshipTypeSigner, Signer: &txnbuild.SignerID{ AccountID: newAccountPair.Address(), SignerAddress: signerKey, }, - }, - &txnbuild.EndSponsoringFutureReserves{}, - } - signers = []*keypair.Full{sponsorPair, newSponsorPair} - txResp, err = itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify operation details - response, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - opRecords := response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 3) - tt.True(opRecords[1].IsTransactionSuccessful()) - - revokeOp := opRecords[1].(operations.RevokeSponsorship) - tt.Equal(newAccountPair.Address(), *revokeOp.SignerAccountID) - tt.Equal(signerKey, *revokeOp.SignerKey) - - // Check effects - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords := effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.SignerSponsorshipUpdated{}, effectRecords[0]) - effect := effectRecords[0].(effects.SignerSponsorshipUpdated) - tt.Equal(sponsorPair.Address(), effect.FormerSponsor) - tt.Equal(newSponsorPair.Address(), effect.NewSponsor) - tt.Equal(signerKey, effect.Signer) - - // Revoke sponsorship - - revoke := txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeSigner, - Signer: &txnbuild.SignerID{ - AccountID: newAccountPair.Address(), - SignerAddress: signerKey, - }, - } - txResp = itest.MustSubmitOperations(newSponsor(), newSponsorPair, &revoke) + } + txResp = itest.MustSubmitOperations(newSponsor, newSponsorPair, &revoke) - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ - ForTransaction: txResp.ID, + effRecords = getEffectsByTx(txResp.ID) + tt.Len(effRecords, 1) + sponsorshipRemoved := effRecords[0].(effects.SignerSponsorshipRemoved) + tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) + tt.Equal(signerKey, sponsorshipRemoved.Signer) }) - tt.NoError(err) - tt.Len(effectsResponse.Embedded.Records, 1) - sponsorshipRemoved := effectsResponse.Embedded.Records[0].(effects.SignerSponsorshipRemoved) - tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) - tt.Equal(signerKey, sponsorshipRemoved.Signer) - -} -func TestSponsoredPreAuthSigner(t *testing.T) { - tt := assert.New(t) - itest := test.NewIntegrationTest(t, protocol14Config) - sponsorPair := itest.Master() - sponsor := itest.MasterAccount - - // Let's create a new account - pairs, _ := itest.CreateAccounts(1, "1000") - newAccountPair := pairs[0] - newAccount := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newAccountPair.Address()} - account, err := itest.Client().AccountDetail(request) - tt.NoError(err) - return &account - } - - // Let's create a preauthorized transaction for the new account - // to add a signer - preAuthOp := &txnbuild.SetOptions{ - Signer: &txnbuild.Signer{ - // unspecific signer - Address: "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", - Weight: 1, - }, - } - txParams := txnbuild.TransactionParams{ - SourceAccount: newAccount(), - Operations: []txnbuild.Operation{preAuthOp}, - BaseFee: txnbuild.MinBaseFee, - Timebounds: txnbuild.NewInfiniteTimeout(), - IncrementSequenceNum: true, - } - preaAuthTx, err := txnbuild.NewTransaction(txParams) - tt.NoError(err) - preAuthHash, err := preaAuthTx.Hash(test.IntegrationNetworkPassphrase) - tt.NoError(err) - - // Let's add a sponsored preauth signer with the following transaction: + // Let's add a sponsored preauth signer with a transaction: // // BeginSponsorship N (Source=sponsor) // SetOptionsSigner preAuthHash (Source=N) // EndSponsorship (Source=N) - preAuthSignerKey := xdr.SignerKey{ - Type: xdr.SignerKeyTypeSignerKeyTypePreAuthTx, - PreAuthTx: (*xdr.Uint256)(&preAuthHash), - } - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SponsoredID: newAccountPair.Address(), - }, + t.Run("PreAuthSigner", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(2, "1000") + sponsorPair, sponsor := keys[0], accounts[0] + newAccountPair, newAccount := keys[1], accounts[1] + + // unspecified signer + randomSigner := keypair.MustRandom().Address() - &txnbuild.SetOptions{ - SourceAccount: newAccount(), + // Let's create a preauthorized transaction for the new account + // to add a signer + preAuthOp := &txnbuild.SetOptions{ Signer: &txnbuild.Signer{ - Address: preAuthSignerKey.Address(), + Address: randomSigner, Weight: 1, }, - }, - &txnbuild.EndSponsoringFutureReserves{ - SourceAccount: newAccount(), - }, - } + } + txParams := txnbuild.TransactionParams{ + SourceAccount: newAccount, + Operations: []txnbuild.Operation{preAuthOp}, + BaseFee: txnbuild.MinBaseFee, + Timebounds: txnbuild.NewInfiniteTimeout(), + IncrementSequenceNum: true, + } + preaAuthTx, err := txnbuild.NewTransaction(txParams) + tt.NoError(err) + preAuthHash, err := preaAuthTx.Hash(test.IntegrationNetworkPassphrase) + tt.NoError(err) + preAuthTxB64, err := preaAuthTx.Base64() + tt.NoError(err) - signers := []*keypair.Full{sponsorPair, newAccountPair} - txResp, err := itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) + // Add a sponsored preauth signer with the above transaction. + preAuthSignerKey := xdr.SignerKey{ + Type: xdr.SignerKeyTypeSignerKeyTypePreAuthTx, + PreAuthTx: (*xdr.Uint256)(&preAuthHash), + } + ops := sponsorOperations(newAccountPair.Address(), + &txnbuild.SetOptions{ + SourceAccount: newAccount, + Signer: &txnbuild.Signer{ + Address: preAuthSignerKey.Address(), + Weight: 1, + }, + }) - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) + signers := []*keypair.Full{sponsorPair, newAccountPair} + txResp, err := itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) - // Verify that the preauth signer was incorporated - preAuthSignerAdded := func() bool { - for _, signer := range newAccount().(*protocol.Account).Signers { - if preAuthSignerKey.Address() == signer.Key { - return true + // Verify that the preauth signer was incorporated + preAuthSignerAdded := func() bool { + for _, signer := range itest.MustGetAccount(newAccountPair).Signers { + if preAuthSignerKey.Address() == signer.Key { + return true + } } + return false } - return false - } - tt.Eventually(preAuthSignerAdded, time.Second*10, time.Millisecond*100) + tt.Condition(preAuthSignerAdded) - // Check effects and details of the SetOptions operation - operationsResponse, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - tt.Len(operationsResponse.Embedded.Records, 3) - setOptionsOp := operationsResponse.Embedded.Records[1].(operations.SetOptions) - tt.Equal(sponsorPair.Address(), setOptionsOp.Sponsor) + // Check effects and details of the SetOptions operation + opRecords := getOperationsByTx(txResp.Hash) + setOptionsOp := opRecords[1].(operations.SetOptions) + tt.Equal(sponsorPair.Address(), setOptionsOp.Sponsor) - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ - ForOperation: setOptionsOp.GetID(), - }) - tt.NoError(err) - if tt.Len(effectsResponse.Embedded.Records, 2) { - signerSponsorshipEffect := effectsResponse.Embedded.Records[1].(effects.SignerSponsorshipCreated) + effRecords := getEffectsByOp(setOptionsOp.GetID()) + signerSponsorshipEffect := effRecords[1].(effects.SignerSponsorshipCreated) tt.Equal(sponsorPair.Address(), signerSponsorshipEffect.Sponsor) tt.Equal(preAuthSignerKey.Address(), signerSponsorshipEffect.Signer) - } - // Submit the preauthorized transaction - preAuthTxB64, err := preaAuthTx.Base64() - tt.NoError(err) - txResp, err = itest.Client().SubmitTransactionXDR(preAuthTxB64) - tt.NoError(err) - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify that the new signer was incorporated and that the preauth signer was removed - preAuthSignerAdded = func() bool { - signers := newAccount().(*protocol.Account).Signers - if len(signers) != 2 { - return false - } - for _, signer := range signers { - if "GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML" == signer.Key { - return true - } - } - return false - } - tt.Eventually(preAuthSignerAdded, time.Second*10, time.Millisecond*100) + // Submit the preauthorized transaction + var txResult xdr.TransactionResult + tt.NoError(err) + txResp, err = client.SubmitTransactionXDR(preAuthTxB64) + tt.NoError(err) + err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) + tt.NoError(err) + tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - // Check effects - // Disabled since the effects processor doesn't process transaction-level changes - // See https://github.com/stellar/go/pull/3050#discussion_r493651644 - /* - operationsResponse, err = itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.Len(operationsResponse.Embedded.Records, 1) - setOptionsOp = operationsResponse.Embedded.Records[0].(operations.SetOptions) - - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - if tt.Len(effectsResponse.Embedded.Records, 2) { - signerSponsorshipEffect := effectsResponse.Embedded.Records[1].(effects.SignerSponsorshipRemoved) - tt.Equal(sponsorPair.Address(), signerSponsorshipEffect.FormerSponsor) - tt.Equal("GC3C4AKRBQLHOJ45U4XG35ESVWRDECWO5XLDGYADO6DPR3L7KIDVUMML", signerSponsorshipEffect.Signer) + // Verify that the new signer was incorporated and that the preauth signer was removed + preAuthSignerAdded = func() bool { + signers := itest.MustGetAccount(newAccountPair).Signers + if len(signers) != 2 { + return false } - */ - -} + for _, signer := range signers { + if signer.Key == randomSigner { + return true + } + } + return false + } + tt.Condition(preAuthSignerAdded) -func TestSponsoredData(t *testing.T) { - tt := assert.New(t) - itest := test.NewIntegrationTest(t, protocol14Config) - sponsorPair := itest.Master() - sponsor := itest.MasterAccount - - // Let's create a new account - pairs, _ := itest.CreateAccounts(1, "1000") - newAccountPair := pairs[0] - newAccount := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newAccountPair.Address()} - account, err := itest.Client().AccountDetail(request) - tt.NoError(err) - return &account - } + // We don't check effects because we don't process transaction-level changes + // See https://github.com/stellar/go/pull/3050#discussion_r493651644 + }) // Let's add a sponsored data entry // // BeginSponsorship N (Source=sponsor) // ManageData "SponsoredData"="SponsoredValue" (Source=N) // EndSponsorship (Source=N) - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SponsoredID: newAccountPair.Address(), - }, - &txnbuild.ManageData{ - Name: "SponsoredData", - Value: []byte("SponsoredValue"), - SourceAccount: newAccount(), - }, - &txnbuild.EndSponsoringFutureReserves{ - SourceAccount: newAccount(), - }, - } - - signers := []*keypair.Full{sponsorPair, newAccountPair} - txResp, err := itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) + t.Run("Data", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(3, "1000") + sponsorPair, sponsor := keys[0], accounts[0] + newAccountPair, newAccount := keys[1], accounts[1] + + ops := sponsorOperations(newAccountPair.Address(), + &txnbuild.ManageData{ + Name: "SponsoredData", + Value: []byte("SponsoredValue"), + SourceAccount: newAccount, + }) - // Verify that the data was incorporated - dataAdded := func() bool { - data := newAccount().(*protocol.Account).Data - if value, ok := data["SponsoredData"]; ok { - decoded, e := base64.StdEncoding.DecodeString(value) - tt.NoError(e) - if string(decoded) == "SponsoredValue" { - return true + signers := []*keypair.Full{sponsorPair, newAccountPair} + txResp, err := itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify that the data was incorporated + dataAdded := func() bool { + data := itest.MustGetAccount(newAccountPair).Data + if value, ok := data["SponsoredData"]; ok { + decoded, e := base64.StdEncoding.DecodeString(value) + tt.NoError(e) + if string(decoded) == "SponsoredValue" { + return true + } } + return false } - return false - } - tt.Eventually(dataAdded, time.Second*10, time.Millisecond*100) + tt.Condition(dataAdded) - // Check effects and details of the ManageData operation - operationsResponse, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - tt.Len(operationsResponse.Embedded.Records, 3) - manageDataOp := operationsResponse.Embedded.Records[1].(operations.ManageData) - tt.Equal(sponsorPair.Address(), manageDataOp.Sponsor) + // Check effects and details of the ManageData operation + opRecords := getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 3) + manageDataOp := opRecords[1].(operations.ManageData) + tt.Equal(sponsorPair.Address(), manageDataOp.Sponsor) - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ - ForOperation: manageDataOp.GetID(), - }) - tt.NoError(err) - if tt.Len(effectsResponse.Embedded.Records, 2) { - dataSponsorshipEffect := effectsResponse.Embedded.Records[1].(effects.DataSponsorshipCreated) + effRecords := getEffectsByOp(manageDataOp.GetID()) + tt.Len(effRecords, 2) + dataSponsorshipEffect := effRecords[1].(effects.DataSponsorshipCreated) tt.Equal(sponsorPair.Address(), dataSponsorshipEffect.Sponsor) tt.Equal(newAccountPair.Address(), dataSponsorshipEffect.Account) tt.Equal("SponsoredData", dataSponsorshipEffect.DataName) - } - // Update sponsor + // Update sponsor - pairs, _ = itest.CreateAccounts(1, "1000") - newSponsorPair := pairs[0] - newSponsor := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newSponsorPair.Address()} - account, e := itest.Client().AccountDetail(request) - tt.NoError(e) - return &account - } - ops = []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: newSponsor(), - SponsoredID: sponsorPair.Address(), - }, - &txnbuild.RevokeSponsorship{ + newSponsorPair, newSponsor := keys[2], accounts[2] + ops = []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{ + SourceAccount: newSponsor, + SponsoredID: sponsorPair.Address(), + }, + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeData, + Data: &txnbuild.DataID{ + Account: newAccountPair.Address(), + DataName: "SponsoredData", + }, + }, + &txnbuild.EndSponsoringFutureReserves{}, + } + signers = []*keypair.Full{sponsorPair, newSponsorPair} + txResp, err = itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify operation details + opRecords = getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 3) + tt.True(opRecords[1].IsTransactionSuccessful()) + + revokeOp := opRecords[1].(operations.RevokeSponsorship) + tt.Equal(newAccountPair.Address(), *revokeOp.DataAccountID) + tt.Equal("SponsoredData", *revokeOp.DataName) + + // Check effects + effRecords = getEffectsByOp(revokeOp.ID) + tt.Len(effRecords, 1) + effect := effRecords[0].(effects.DataSponsorshipUpdated) + tt.Equal(sponsorPair.Address(), effect.FormerSponsor) + tt.Equal(newSponsorPair.Address(), effect.NewSponsor) + tt.Equal("SponsoredData", effect.DataName) + + // Revoke sponsorship + + revoke := txnbuild.RevokeSponsorship{ SponsorshipType: txnbuild.RevokeSponsorshipTypeData, Data: &txnbuild.DataID{ Account: newAccountPair.Address(), DataName: "SponsoredData", }, - }, - &txnbuild.EndSponsoringFutureReserves{}, - } - signers = []*keypair.Full{sponsorPair, newSponsorPair} - txResp, err = itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify operation details - response, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - opRecords := response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 3) - tt.True(opRecords[1].IsTransactionSuccessful()) - - revokeOp := opRecords[1].(operations.RevokeSponsorship) - tt.Equal(newAccountPair.Address(), *revokeOp.DataAccountID) - tt.Equal("SponsoredData", *revokeOp.DataName) - - // Check effects - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords := effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.DataSponsorshipUpdated{}, effectRecords[0]) - effect := effectRecords[0].(effects.DataSponsorshipUpdated) - tt.Equal(sponsorPair.Address(), effect.FormerSponsor) - tt.Equal(newSponsorPair.Address(), effect.NewSponsor) - tt.Equal("SponsoredData", effect.DataName) - - // Revoke sponsorship - - revoke := txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeData, - Data: &txnbuild.DataID{ - Account: newAccountPair.Address(), - DataName: "SponsoredData", - }, - } - txResp = itest.MustSubmitOperations(newSponsor(), newSponsorPair, &revoke) + } + txResp = itest.MustSubmitOperations(newSponsor, newSponsorPair, &revoke) - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ - ForTransaction: txResp.ID, + effRecords = getEffectsByTx(txResp.ID) + tt.Len(effRecords, 1) + sponsorshipRemoved := effRecords[0].(effects.DataSponsorshipRemoved) + tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) + tt.Equal("SponsoredData", sponsorshipRemoved.DataName) }) - tt.NoError(err) - tt.Len(effectsResponse.Embedded.Records, 1) - sponsorshipRemoved := effectsResponse.Embedded.Records[0].(effects.DataSponsorshipRemoved) - tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) - tt.Equal("SponsoredData", sponsorshipRemoved.DataName) - -} - -func TestSponsoredTrustlineAndOffer(t *testing.T) { - tt := assert.New(t) - itest := test.NewIntegrationTest(t, protocol14Config) - sponsorPair := itest.Master() - sponsor := itest.MasterAccount - - // Let's create a new account - pairs, _ := itest.CreateAccounts(1, "1000") - newAccountPair := pairs[0] - newAccount := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newAccountPair.Address()} - account, err := itest.Client().AccountDetail(request) - tt.NoError(err) - return &account - } // Let's add a sponsored trustline and offer // @@ -698,315 +498,280 @@ func TestSponsoredTrustlineAndOffer(t *testing.T) { // Change Trust (ABC, sponsor) (Source=N) // ManageSellOffer Buying (ABC, sponsor) (Source=N) // EndSponsorship (Source=N) - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SponsoredID: newAccountPair.Address(), - }, - &txnbuild.ChangeTrust{ - SourceAccount: newAccount(), - Line: txnbuild.CreditAsset{"ABCD", sponsorPair.Address()}, - Limit: txnbuild.MaxTrustlineLimit, - }, - &txnbuild.ManageSellOffer{ - SourceAccount: newAccount(), - Selling: txnbuild.NativeAsset{}, - Buying: txnbuild.CreditAsset{"ABCD", sponsorPair.Address()}, - Amount: "3", - Price: "1", - }, - &txnbuild.EndSponsoringFutureReserves{ - SourceAccount: newAccount(), - }, - } + t.Run("TrustlineAndOffer", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(3, "1000") + sponsorPair, sponsor := keys[0], accounts[0] + newAccountPair, newAccount := keys[1], accounts[1] + + asset := txnbuild.CreditAsset{Code: "ABCD", Issuer: sponsorPair.Address()} + canonicalAsset := fmt.Sprintf("%s:%s", asset.Code, asset.Issuer) + + ops := sponsorOperations(newAccountPair.Address(), + &txnbuild.ChangeTrust{ + SourceAccount: newAccount, + Line: asset, + Limit: txnbuild.MaxTrustlineLimit, + }, + &txnbuild.ManageSellOffer{ + SourceAccount: newAccount, + Selling: txnbuild.NativeAsset{}, + Buying: asset, + Amount: "3", + Price: "1", + }) - signers := []*keypair.Full{sponsorPair, newAccountPair} - txResp, err := itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify that the offer was incorporated correctly - trustlineAdded := func() bool { - for _, balance := range newAccount().(*protocol.Account).Balances { - if balance.Issuer == sponsorPair.Address() { - tt.Equal("ABCD", balance.Code) - tt.Equal(sponsorPair.Address(), balance.Sponsor) + signers := []*keypair.Full{sponsorPair, newAccountPair} + txResp, err := itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify that the offer was incorporated correctly + trustlineAdded := func() bool { + for _, balance := range itest.MustGetAccount(newAccountPair).Balances { + if balance.Issuer == sponsorPair.Address() { + tt.Equal(asset.Code, balance.Code) + tt.Equal(sponsorPair.Address(), balance.Sponsor) + return true + } + } + return false + } + tt.Condition(trustlineAdded) + + // Check the details of the ManageSellOffer operation + // (there are no effects, which is intentional) + opRecords := getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 4) + changeTrust := opRecords[1].(operations.ChangeTrust) + tt.Equal(sponsorPair.Address(), changeTrust.Sponsor) + + // Verify that the offer was incorporated correctly + var offer protocol.Offer + offerAdded := func() bool { + offers, e := client.Offers(sdk.OfferRequest{ + ForAccount: newAccountPair.Address(), + }) + tt.NoError(e) + if tt.Len(offers.Embedded.Records, 1) { + offer = offers.Embedded.Records[0] + tt.Equal(sponsorPair.Address(), offer.Buying.Issuer) + tt.Equal(asset.Code, offer.Buying.Code) + tt.Equal(sponsorPair.Address(), offer.Sponsor) return true } + return false } - return false - } - tt.Eventually(trustlineAdded, time.Second*10, time.Millisecond*100) + tt.Condition(offerAdded) - // Check the details of the ManageSellOffer operation - // (there are no effects, which is intentional) - operationsResponse, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - tt.Len(operationsResponse.Embedded.Records, 4) - changeTrust := operationsResponse.Embedded.Records[1].(operations.ChangeTrust) - tt.Equal(sponsorPair.Address(), changeTrust.Sponsor) - - // Verify that the offer was incorporated correctly - var offer protocol.Offer - offerAdded := func() bool { - offers, e := itest.Client().Offers(sdk.OfferRequest{ - ForAccount: newAccountPair.Address(), - }) - tt.NoError(e) - if len(offers.Embedded.Records) == 1 { - offer = offers.Embedded.Records[0] - tt.Equal(sponsorPair.Address(), offer.Buying.Issuer) - tt.Equal("ABCD", offer.Buying.Code) - tt.Equal(sponsorPair.Address(), offer.Sponsor) - return true + // Check the details of the ManageSellOffer operation + // (there are no effects, which is intentional) + manageOffer := opRecords[2].(operations.ManageSellOffer) + tt.Equal(sponsorPair.Address(), manageOffer.Sponsor) + + // Update sponsor + + newSponsorPair, newSponsor := keys[2], accounts[2] + + ops = []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{ + SourceAccount: newSponsor, + SponsoredID: sponsorPair.Address(), + }, + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeOffer, + Offer: &txnbuild.OfferID{ + SellerAccountAddress: offer.Seller, + OfferID: offer.ID, + }, + }, + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine, + TrustLine: &txnbuild.TrustLineID{ + Account: newAccountPair.Address(), + Asset: asset, + }, + }, + &txnbuild.EndSponsoringFutureReserves{}, } - return false - } - tt.Eventually(offerAdded, time.Second*10, time.Millisecond*100) - - // Check the details of the ManageSellOffer operation - // (there are no effects, which is intentional) - manageOffer := operationsResponse.Embedded.Records[2].(operations.ManageSellOffer) - tt.Equal(sponsorPair.Address(), manageOffer.Sponsor) - - // Update sponsor - - pairs, _ = itest.CreateAccounts(1, "1000") - newSponsorPair := pairs[0] - newSponsor := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newSponsorPair.Address()} - account, e := itest.Client().AccountDetail(request) - tt.NoError(e) - return &account - } - ops = []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: newSponsor(), - SponsoredID: sponsorPair.Address(), - }, - &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeOffer, - Offer: &txnbuild.OfferID{ - SellerAccountAddress: offer.Seller, - OfferID: offer.ID, + signers = []*keypair.Full{sponsorPair, newSponsorPair} + txResp, err = itest.SubmitMultiSigOperations(sponsor, signers, ops...) + itest.LogFailedTx(txResp, err) + + // Verify operation details + opRecords = getOperationsByTx(txResp.Hash) + tt.Len(opRecords, 4) + + tt.True(opRecords[1].IsTransactionSuccessful()) + revokeOp := opRecords[1].(operations.RevokeSponsorship) + tt.Equal(offer.ID, *revokeOp.OfferID) + + tt.True(opRecords[2].IsTransactionSuccessful()) + revokeOp = opRecords[2].(operations.RevokeSponsorship) + tt.Equal(newAccountPair.Address(), *revokeOp.TrustlineAccountID) + tt.Equal("ABCD:"+sponsorPair.Address(), *revokeOp.TrustlineAsset) + + // Check effects + effRecords := getEffectsByOp(revokeOp.ID) + tt.Len(effRecords, 1) + effect := effRecords[0].(effects.TrustlineSponsorshipUpdated) + tt.Equal(sponsorPair.Address(), effect.FormerSponsor) + tt.Equal(newSponsorPair.Address(), effect.NewSponsor) + + // Revoke sponsorship + ops = []txnbuild.Operation{ + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeOffer, + Offer: &txnbuild.OfferID{ + SellerAccountAddress: offer.Seller, + OfferID: offer.ID, + }, }, - }, - &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine, - TrustLine: &txnbuild.TrustLineID{ - Account: newAccountPair.Address(), - Asset: txnbuild.CreditAsset{ - Code: "ABCD", - Issuer: sponsorPair.Address(), + &txnbuild.RevokeSponsorship{ + SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine, + TrustLine: &txnbuild.TrustLineID{ + Account: newAccountPair.Address(), + Asset: asset, }, }, - }, - &txnbuild.EndSponsoringFutureReserves{}, - } - signers = []*keypair.Full{sponsorPair, newSponsorPair} - txResp, err = itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) - - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) - - // Verify operation details - response, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, + } + txResp = itest.MustSubmitOperations(newSponsor, newSponsorPair, ops...) + + // There are intentionally no effects when revoking an Offer + effRecords = getEffectsByTx(txResp.ID) + tt.Len(effRecords, 1) + sponsorshipRemoved := effRecords[0].(effects.TrustlineSponsorshipRemoved) + tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) + tt.Equal(canonicalAsset, sponsorshipRemoved.Asset) }) - opRecords := response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 4) - - tt.True(opRecords[1].IsTransactionSuccessful()) - revokeOp := opRecords[1].(operations.RevokeSponsorship) - tt.Equal(offer.ID, *revokeOp.OfferID) - - tt.True(opRecords[2].IsTransactionSuccessful()) - revokeOp = opRecords[2].(operations.RevokeSponsorship) - tt.Equal(newAccountPair.Address(), *revokeOp.TrustlineAccountID) - tt.Equal("ABCD:"+sponsorPair.Address(), *revokeOp.TrustlineAsset) - - // Check effects - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords := effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.TrustlineSponsorshipUpdated{}, effectRecords[0]) - effect := effectRecords[0].(effects.TrustlineSponsorshipUpdated) - tt.Equal(sponsorPair.Address(), effect.FormerSponsor) - tt.Equal(newSponsorPair.Address(), effect.NewSponsor) - - // Revoke sponsorship - ops = []txnbuild.Operation{ - &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeOffer, - Offer: &txnbuild.OfferID{ - SellerAccountAddress: offer.Seller, - OfferID: offer.ID, + + // + // Confirms the operations, effects, and functionality of combining the + // CAP-23 and CAP-33 features together as part of Protocol 14. + // + t.Run("ClaimableBalance", func(t *testing.T) { + keys, accounts := itest.CreateAccounts(2, "50") + sponsorPair, sponsor := keys[0], accounts[0] + sponsoreePair, sponsoree := keys[1], accounts[1] + + ops := []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{ + SourceAccount: sponsor, + SponsoredID: sponsoreePair.Address(), }, - }, - &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine, - TrustLine: &txnbuild.TrustLineID{ - Account: newAccountPair.Address(), - Asset: txnbuild.CreditAsset{ - Code: "ABCD", - Issuer: sponsorPair.Address(), + &txnbuild.CreateClaimableBalance{ + SourceAccount: sponsoree, + Destinations: []txnbuild.Claimant{ + txnbuild.NewClaimant(sponsorPair.Address(), nil), + txnbuild.NewClaimant(sponsoreePair.Address(), nil), }, + Amount: "25", + Asset: txnbuild.NativeAsset{}, }, - }, - } - txResp = itest.MustSubmitOperations(newSponsor(), newSponsorPair, ops...) + &txnbuild.EndSponsoringFutureReserves{}, + } - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ - ForTransaction: txResp.ID, - }) - tt.NoError(err) - // There are intentionally no effects when revoking an Offer - tt.Len(effectsResponse.Embedded.Records, 1) - sponsorshipRemoved := effectsResponse.Embedded.Records[0].(effects.TrustlineSponsorshipRemoved) - tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) - tt.Equal("ABCD:"+sponsorPair.Address(), sponsorshipRemoved.Asset) -} + txResp, err := itest.SubmitMultiSigOperations(sponsoree, + []*keypair.Full{sponsoreePair, sponsorPair}, ops...) + itest.LogFailedTx(txResp, err) -func TestSponsoredClaimableBalance(t *testing.T) { - tt := assert.New(t) - itest := test.NewIntegrationTest(t, protocol14Config) - sponsorPair := itest.Master() - sponsor := itest.MasterAccount - - pairs, _ := itest.CreateAccounts(1, "1000") - newAccountPair := pairs[0] - newAccount := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newAccountPair.Address()} - account, err := itest.Client().AccountDetail(request) + // Establish a baseline for the master account + masterBalance := getAccountXLM(itest, sponsorPair) + + // Check the global /claimable_balances list for success. + balances, err := client.ClaimableBalances(sdk.ClaimableBalanceRequest{}) tt.NoError(err) - return &account - } - ops := []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: sponsor(), - SponsoredID: newAccountPair.Address(), - }, - &txnbuild.CreateClaimableBalance{ - SourceAccount: newAccount(), - Destinations: []txnbuild.Claimant{ - txnbuild.NewClaimant(sponsorPair.Address(), nil), - }, - Amount: "20", - Asset: txnbuild.NativeAsset{}, - }, - &txnbuild.EndSponsoringFutureReserves{}, - } - signers := []*keypair.Full{newAccountPair, sponsorPair} - txResp, err := itest.SubmitMultiSigOperations(newAccount(), signers, ops...) - tt.NoError(err) + claims := balances.Embedded.Records + tt.Len(claims, 1) + balance := claims[0] + tt.Equal(sponsorPair.Address(), balance.Sponsor) + + // Claim the CB and validate balances: + // - sponsoree should go down for fulfilling the CB + // - master should go up for claiming the CB + txResp, err = itest.SubmitOperations(sponsor, sponsorPair, + &txnbuild.ClaimClaimableBalance{BalanceID: claims[0].BalanceID}) + itest.LogFailedTx(txResp, err) + + tt.Lessf(getAccountXLM(itest, sponsoreePair), float64(25), "sponsoree balance didn't decrease") + tt.Greaterf(getAccountXLM(itest, sponsorPair), masterBalance, "master balance didn't increase") + + // Check that operations populate. + expectedOperations := map[string]bool{ + operations.TypeNames[xdr.OperationTypeBeginSponsoringFutureReserves]: false, + operations.TypeNames[xdr.OperationTypeCreateClaimableBalance]: false, + operations.TypeNames[xdr.OperationTypeEndSponsoringFutureReserves]: false, + } - var txResult xdr.TransactionResult - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) + opsPage, err := client.Operations(sdk.OperationRequest{Order: "desc", Limit: 5}) + for _, op := range opsPage.Embedded.Records { + opType := op.GetType() + if _, ok := expectedOperations[opType]; ok { + expectedOperations[opType] = true + t.Logf(" operation %s found", opType) + } + } - balances, err := itest.Client().ClaimableBalances(sdk.ClaimableBalanceRequest{}) - tt.NoError(err) + for expectedType, exists := range expectedOperations { + tt.Truef(exists, "operation %s not found", expectedType) + } - claims := balances.Embedded.Records - tt.Len(claims, 1) - balance := claims[0] - tt.Equal(sponsorPair.Address(), balance.Sponsor) + // Check that effects populate. + expectedEffects := map[string][]uint{ + effects.EffectTypeNames[effects.EffectClaimableBalanceSponsorshipCreated]: []uint{0, 1}, + effects.EffectTypeNames[effects.EffectClaimableBalanceCreated]: []uint{0, 1}, + effects.EffectTypeNames[effects.EffectClaimableBalanceClaimantCreated]: []uint{0, 2}, + effects.EffectTypeNames[effects.EffectClaimableBalanceSponsorshipRemoved]: []uint{0, 1}, + effects.EffectTypeNames[effects.EffectClaimableBalanceClaimed]: []uint{0, 1}, + } - // Check effects and details of the CreateClaimableBalance operation - operationsResponse, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - tt.NoError(err) - tt.Len(operationsResponse.Embedded.Records, 3) - createClaimableBalance := operationsResponse.Embedded.Records[1].(operations.CreateClaimableBalance) - tt.Equal(sponsorPair.Address(), createClaimableBalance.Sponsor) + effectsPage, err := client.Effects(sdk.EffectRequest{Order: "desc", Limit: 100}) + for _, effect := range effectsPage.Embedded.Records { + effectType := effect.GetType() + if _, ok := expectedEffects[effectType]; ok { + expectedEffects[effectType][0] += 1 + t.Logf(" effect %s found", effectType) + } + } - effectsResponse, err := itest.Client().Effects(sdk.EffectRequest{ - ForOperation: createClaimableBalance.GetID(), + for expectedType, counts := range expectedEffects { + actual, needed := counts[0], counts[1] + tt.Equalf(needed, actual, "effect %s not found enough", expectedType) + } }) - tt.NoError(err) - if tt.Len(effectsResponse.Embedded.Records, 4) { - cbSponsorshipEffect := effectsResponse.Embedded.Records[3].(effects.ClaimableBalanceSponsorshipCreated) - tt.Equal(sponsorPair.Address(), cbSponsorshipEffect.Sponsor) - tt.Equal(newAccountPair.Address(), cbSponsorshipEffect.Account) - tt.Equal(balance.BalanceID, cbSponsorshipEffect.BalanceID) - } - - // Update sponsor +} - pairs, _ = itest.CreateAccounts(1, "1000") - newSponsorPair := pairs[0] - newSponsor := func() txnbuild.Account { - request := sdk.AccountRequest{AccountID: newSponsorPair.Address()} - account, e := itest.Client().AccountDetail(request) - tt.NoError(e) - return &account - } - ops = []txnbuild.Operation{ - &txnbuild.BeginSponsoringFutureReserves{ - SourceAccount: newSponsor(), - SponsoredID: sponsorPair.Address(), +// Sandwiches a set of operations between a Begin/End reserve sponsorship. +func sponsorOperations(account string, ops ...txnbuild.Operation) []txnbuild.Operation { + return append(append( + []txnbuild.Operation{ + &txnbuild.BeginSponsoringFutureReserves{SponsoredID: account}, }, - &txnbuild.RevokeSponsorship{ - SponsorshipType: txnbuild.RevokeSponsorshipTypeClaimableBalance, - ClaimableBalance: &balance.BalanceID, + ops...), + &txnbuild.EndSponsoringFutureReserves{ + SourceAccount: &txnbuild.SimpleAccount{AccountID: account}, }, - &txnbuild.EndSponsoringFutureReserves{}, - } - signers = []*keypair.Full{sponsorPair, newSponsorPair} - txResp, err = itest.SubmitMultiSigOperations(sponsor(), signers, ops...) - tt.NoError(err) + ) +} - err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult) - tt.NoError(err) - tt.Equal(xdr.TransactionResultCodeTxSuccess, txResult.Result.Code) +// Returns a function that will find `needle` in `haystack` by ID. +// Designed to be usable by assert.Condition +func findOperationByID(needle string, haystack []operations.Operation) func() bool { + return func() bool { + for _, o := range haystack { + if o.GetID() == needle { + return true + } + } + return false + } +} - // Verify operation details - response, err := itest.Client().Operations(sdk.OperationRequest{ - ForTransaction: txResp.Hash, - }) - opRecords := response.Embedded.Records - tt.NoError(err) - tt.Len(opRecords, 3) - tt.True(opRecords[1].IsTransactionSuccessful()) - - revokeOp := opRecords[1].(operations.RevokeSponsorship) - tt.Equal(balance.BalanceID, *revokeOp.ClaimableBalanceID) - - // Check effects - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ForOperation: revokeOp.ID}) - tt.NoError(err) - effectRecords := effectsResponse.Embedded.Records - tt.Len(effectRecords, 1) - tt.IsType(effects.ClaimableBalanceSponsorshipUpdated{}, effectRecords[0]) - effect := effectRecords[0].(effects.ClaimableBalanceSponsorshipUpdated) - tt.Equal(sponsorPair.Address(), effect.FormerSponsor) - tt.Equal(newSponsorPair.Address(), effect.NewSponsor) - - // It's not possible to explicitly revoke the sponsorship of a claimable balance, - // so we claim the balance instead - claimOp := &txnbuild.ClaimClaimableBalance{ - BalanceID: balance.BalanceID, +// Retrieves the XLM balance for an account. +func getAccountXLM(i *test.IntegrationTest, account *keypair.Full) float64 { + details := i.MustGetAccount(account) + balance, err := strconv.ParseFloat(details.Balances[0].Balance, 64) + if err != nil { + panic(err) } - txResp = itest.MustSubmitOperations(sponsor(), sponsorPair, claimOp) - effectsResponse, err = itest.Client().Effects(sdk.EffectRequest{ - ForTransaction: txResp.ID, - }) - tt.NoError(err) - effectRecords = effectsResponse.Embedded.Records - tt.Len(effectRecords, 3) - sponsorshipRemoved := effectRecords[2].(effects.ClaimableBalanceSponsorshipRemoved) - tt.Equal(newSponsorPair.Address(), sponsorshipRemoved.FormerSponsor) - tt.Equal(balance.BalanceID, sponsorshipRemoved.BalanceID) + return balance } diff --git a/services/horizon/internal/test/integration.go b/services/horizon/internal/test/integration.go index 6b2298df7b..0856c08b43 100644 --- a/services/horizon/internal/test/integration.go +++ b/services/horizon/internal/test/integration.go @@ -256,7 +256,7 @@ func createTestContainer(i *IntegrationTest, image string) error { // If your Internet (or docker.io) is down, integration tests should still try to run. reader, err := i.cli.ImagePull(ctx, "docker.io/"+image, types.ImagePullOptions{}) if err != nil { - t.Log("error pulling docker image") + t.Log(" error pulling docker image") t.Log(" trying to find local image (might be out-dated)") args := filters.NewArgs() @@ -355,7 +355,8 @@ func (i *IntegrationTest) CreateAccounts(count int, initialBalance string) ([]*k } for _, keys := range pairs { - i.t.Logf("Funded %s (%s).\n", keys.Seed(), keys.Address()) + i.t.Logf("Funded %s (%s) with %s XLM.\n", + keys.Seed(), keys.Address(), initialBalance) } return pairs, accounts