diff --git a/txnbuild/CHANGELOG.md b/txnbuild/CHANGELOG.md index 3c89431c46..bbadc08d24 100644 --- a/txnbuild/CHANGELOG.md +++ b/txnbuild/CHANGELOG.md @@ -8,6 +8,7 @@ file. This project adheres to [Semantic Versioning](http://semver.org/). * GenericTransaction, Transaction, and FeeBumpTransaction now implement encoding.TextMarshaler and encoding.TextUnmarshaler. +* Adds 5-minute grace period to `transaction.ReadChallengeTx`'s minimum time bound constraint. ([#3824](https://github.com/stellar/go/pull/3824)) ## [v7.1.1](https://github.com/stellar/go/releases/tag/horizonclient-v7.1.1) - 2021-06-25 diff --git a/txnbuild/transaction.go b/txnbuild/transaction.go index 35777e767a..1199ca5367 100644 --- a/txnbuild/transaction.go +++ b/txnbuild/transaction.go @@ -1098,8 +1098,10 @@ func ReadChallengeTx(challengeTx, serverAccountID, network, webAuthDomain string if tx.Timebounds().MaxTime == TimeoutInfinite { return tx, clientAccountID, matchedHomeDomain, errors.New("transaction requires non-infinite timebounds") } + // Apply a grace period to the challenge MinTime to account for clock drift between the server and client + var gracePeriod int64 = 5 * 60 // seconds currentTime := time.Now().UTC().Unix() - if currentTime < tx.Timebounds().MinTime || currentTime > tx.Timebounds().MaxTime { + if currentTime+gracePeriod < tx.Timebounds().MinTime || currentTime > tx.Timebounds().MaxTime { return tx, clientAccountID, matchedHomeDomain, errors.Errorf("transaction is not within range of the specified timebounds (currentTime=%d, MinTime=%d, MaxTime=%d)", currentTime, tx.Timebounds().MinTime, tx.Timebounds().MaxTime) } diff --git a/txnbuild/transaction_test.go b/txnbuild/transaction_test.go index f1b6274ec8..116706b34a 100644 --- a/txnbuild/transaction_test.go +++ b/txnbuild/transaction_test.go @@ -2023,6 +2023,79 @@ func TestReadChallengeTx_invalidTimeboundsOutsideRange(t *testing.T) { assert.Regexp(t, "transaction is not within range of the specified timebounds", err.Error()) } +func TestReadChallengeTx_validTimeboundsWithGracePeriod(t *testing.T) { + serverKP := newKeypair0() + clientKP := newKeypair1() + txSource := NewSimpleAccount(serverKP.Address(), -1) + op := ManageData{ + SourceAccount: clientKP.Address(), + Name: "testanchor.stellar.org auth", + Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))), + } + webAuthDomainOp := ManageData{ + SourceAccount: serverKP.Address(), + Name: "web_auth_domain", + Value: []byte("testwebauth.stellar.org"), + } + unixNow := time.Now().UTC().Unix() + tx, err := NewTransaction( + TransactionParams{ + SourceAccount: &txSource, + IncrementSequenceNum: true, + Operations: []Operation{&op, &webAuthDomainOp}, + BaseFee: MinBaseFee, + Timebounds: NewTimebounds(unixNow+5*59, unixNow+60*60), + }, + ) + assert.NoError(t, err) + + tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP) + assert.NoError(t, err) + tx64, err := tx.Base64() + require.NoError(t, err) + readTx, readClientAccountID, _, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}) + assert.Equal(t, tx, readTx) + assert.Equal(t, clientKP.Address(), readClientAccountID) + assert.NoError(t, err) +} + +func TestReadChallengeTx_invalidTimeboundsWithGracePeriod(t *testing.T) { + serverKP := newKeypair0() + clientKP := newKeypair1() + txSource := NewSimpleAccount(serverKP.Address(), -1) + op := ManageData{ + SourceAccount: clientKP.Address(), + Name: "testanchor.stellar.org auth", + Value: []byte(base64.StdEncoding.EncodeToString(make([]byte, 48))), + } + webAuthDomainOp := ManageData{ + SourceAccount: serverKP.Address(), + Name: "web_auth_domain", + Value: []byte("testwebauth.stellar.org"), + } + unixNow := time.Now().UTC().Unix() + tx, err := NewTransaction( + TransactionParams{ + SourceAccount: &txSource, + IncrementSequenceNum: true, + Operations: []Operation{&op, &webAuthDomainOp}, + BaseFee: MinBaseFee, + Timebounds: NewTimebounds(unixNow+5*61, unixNow+60*60), + }, + ) + assert.NoError(t, err) + + tx, err = tx.Sign(network.TestNetworkPassphrase, serverKP) + assert.NoError(t, err) + tx64, err := tx.Base64() + require.NoError(t, err) + readTx, readClientAccountID, _, err := ReadChallengeTx(tx64, serverKP.Address(), network.TestNetworkPassphrase, "testwebauth.stellar.org", []string{"testanchor.stellar.org"}) + assert.Equal(t, tx, readTx) + assert.Equal(t, "", readClientAccountID) + assert.Error(t, err) + assert.Regexp(t, "transaction is not within range of the specified timebounds", err.Error()) +} + func TestReadChallengeTx_invalidOperationWrongType(t *testing.T) { serverKP := newKeypair0() clientKP := newKeypair1()