Update API: PostWallet no longer returns a wallet
This commit is contained in:
parent
94114ec36d
commit
3d492d8b86
9 changed files with 205 additions and 365 deletions
|
@ -78,6 +78,8 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
`{"email": "abc@example.com", "password": "123"}`,
|
`{"email": "abc@example.com", "password": "123"}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
checkStatusCode(t, statusCode, responseBody, http.StatusCreated)
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// Get auth token - device 1
|
// Get auth token - device 1
|
||||||
////////////////////
|
////////////////////
|
||||||
|
@ -130,13 +132,13 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
// Put first wallet - device 1
|
// Put first wallet - device 1
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
var walletResponse WalletResponse
|
var walletPostResponse struct{}
|
||||||
responseBody, statusCode = request(
|
responseBody, statusCode = request(
|
||||||
t,
|
t,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
s.postWallet,
|
s.postWallet,
|
||||||
PathWallet,
|
PathWallet,
|
||||||
&walletResponse,
|
&walletPostResponse,
|
||||||
fmt.Sprintf(`{
|
fmt.Sprintf(`{
|
||||||
"token": "%s",
|
"token": "%s",
|
||||||
"encryptedWallet": "my-encrypted-wallet-1",
|
"encryptedWallet": "my-encrypted-wallet-1",
|
||||||
|
@ -147,34 +149,30 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// Get wallet - device 2
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
var walletGetResponse WalletResponse
|
||||||
|
responseBody, statusCode = request(
|
||||||
|
t,
|
||||||
|
http.MethodGet,
|
||||||
|
s.getWallet,
|
||||||
|
fmt.Sprintf("%s?token=%s", PathWallet, authToken2.Token),
|
||||||
|
&walletGetResponse,
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
|
||||||
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
expectedResponse := WalletResponse{
|
expectedResponse := WalletResponse{
|
||||||
EncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-1"),
|
EncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-1"),
|
||||||
Sequence: wallet.Sequence(1),
|
Sequence: wallet.Sequence(1),
|
||||||
Hmac: wallet.WalletHmac("my-hmac-1"),
|
Hmac: wallet.WalletHmac("my-hmac-1"),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(walletResponse, expectedResponse) {
|
if !reflect.DeepEqual(walletGetResponse, expectedResponse) {
|
||||||
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletResponse)
|
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletGetResponse)
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////
|
|
||||||
// Get wallet - device 2
|
|
||||||
////////////////////
|
|
||||||
|
|
||||||
responseBody, statusCode = request(
|
|
||||||
t,
|
|
||||||
http.MethodGet,
|
|
||||||
s.getWallet,
|
|
||||||
fmt.Sprintf("%s?token=%s", PathWallet, authToken2.Token),
|
|
||||||
&walletResponse,
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
|
||||||
|
|
||||||
// Expect the same response getting from device 2 as when posting from device 1
|
|
||||||
if !reflect.DeepEqual(walletResponse, expectedResponse) {
|
|
||||||
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletResponse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
|
@ -186,7 +184,7 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
s.postWallet,
|
s.postWallet,
|
||||||
PathWallet,
|
PathWallet,
|
||||||
&walletResponse,
|
&walletPostResponse,
|
||||||
fmt.Sprintf(`{
|
fmt.Sprintf(`{
|
||||||
"token": "%s",
|
"token": "%s",
|
||||||
"encryptedWallet": "my-encrypted-wallet-2",
|
"encryptedWallet": "my-encrypted-wallet-2",
|
||||||
|
@ -197,16 +195,6 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
expectedResponse = WalletResponse{
|
|
||||||
EncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-2"),
|
|
||||||
Sequence: wallet.Sequence(2),
|
|
||||||
Hmac: wallet.WalletHmac("my-hmac-2"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(walletResponse, expectedResponse) {
|
|
||||||
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// Get wallet - device 1
|
// Get wallet - device 1
|
||||||
////////////////////
|
////////////////////
|
||||||
|
@ -216,14 +204,20 @@ func TestIntegrationWalletUpdates(t *testing.T) {
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
s.getWallet,
|
s.getWallet,
|
||||||
fmt.Sprintf("%s?token=%s", PathWallet, authToken1.Token),
|
fmt.Sprintf("%s?token=%s", PathWallet, authToken1.Token),
|
||||||
&walletResponse,
|
&walletGetResponse,
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
checkStatusCode(t, statusCode, responseBody)
|
checkStatusCode(t, statusCode, responseBody)
|
||||||
|
|
||||||
|
expectedResponse = WalletResponse{
|
||||||
|
EncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-2"),
|
||||||
|
Sequence: wallet.Sequence(2),
|
||||||
|
Hmac: wallet.WalletHmac("my-hmac-2"),
|
||||||
|
}
|
||||||
|
|
||||||
// Expect the same response getting from device 2 as when posting from device 1
|
// Expect the same response getting from device 2 as when posting from device 1
|
||||||
if !reflect.DeepEqual(walletResponse, expectedResponse) {
|
if !reflect.DeepEqual(walletGetResponse, expectedResponse) {
|
||||||
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletResponse)
|
t.Fatalf("Unexpected response values. want: %+v got: %+v", expectedResponse, walletGetResponse)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
// TODO proper doc comments!
|
// TODO proper doc comments!
|
||||||
|
|
||||||
const ApiVersion = "1"
|
const ApiVersion = "2"
|
||||||
const PathPrefix = "/api/" + ApiVersion
|
const PathPrefix = "/api/" + ApiVersion
|
||||||
|
|
||||||
const PathAuthToken = PathPrefix + "/auth/full"
|
const PathAuthToken = PathPrefix + "/auth/full"
|
||||||
|
|
|
@ -67,7 +67,6 @@ type TestStore struct {
|
||||||
TestEncryptedWallet wallet.EncryptedWallet
|
TestEncryptedWallet wallet.EncryptedWallet
|
||||||
TestSequence wallet.Sequence
|
TestSequence wallet.Sequence
|
||||||
TestHmac wallet.WalletHmac
|
TestHmac wallet.WalletHmac
|
||||||
TestSequenceCorrect bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestStore) SaveToken(authToken *auth.AuthToken) error {
|
func (s *TestStore) SaveToken(authToken *auth.AuthToken) error {
|
||||||
|
@ -95,16 +94,9 @@ func (s *TestStore) SetWallet(
|
||||||
encryptedWallet wallet.EncryptedWallet,
|
encryptedWallet wallet.EncryptedWallet,
|
||||||
sequence wallet.Sequence,
|
sequence wallet.Sequence,
|
||||||
hmac wallet.WalletHmac,
|
hmac wallet.WalletHmac,
|
||||||
) (latestEncryptedWallet wallet.EncryptedWallet, latestSequence wallet.Sequence, latestHmac wallet.WalletHmac, sequenceCorrect bool, err error) {
|
) (err error) {
|
||||||
s.Called.SetWallet = SetWalletCall{encryptedWallet, sequence, hmac}
|
s.Called.SetWallet = SetWalletCall{encryptedWallet, sequence, hmac}
|
||||||
err = s.Errors.SetWallet
|
return s.Errors.SetWallet
|
||||||
if err == nil {
|
|
||||||
latestEncryptedWallet = s.TestEncryptedWallet
|
|
||||||
latestSequence = s.TestSequence
|
|
||||||
latestHmac = s.TestHmac
|
|
||||||
sequenceCorrect = s.TestSequenceCorrect
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TestStore) GetWallet(userId auth.UserId) (encryptedWallet wallet.EncryptedWallet, sequence wallet.Sequence, hmac wallet.WalletHmac, err error) {
|
func (s *TestStore) GetWallet(userId auth.UserId) (encryptedWallet wallet.EncryptedWallet, sequence wallet.Sequence, hmac wallet.WalletHmac, err error) {
|
||||||
|
@ -229,7 +221,7 @@ func TestServerHelperGetGetDataSuccess(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestServerHelperGetGetDataErrors(t *testing.T) {
|
func TestServerHelperGetGetDataErrors(t *testing.T) {
|
||||||
// Only error right now is if you do a POST request
|
// Only error right now is if you do a POST request
|
||||||
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
req := httptest.NewRequest(http.MethodPost, "/test", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
success := getGetData(w, req)
|
success := getGetData(w, req)
|
||||||
|
|
|
@ -27,7 +27,6 @@ type WalletResponse struct {
|
||||||
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
EncryptedWallet wallet.EncryptedWallet `json:"encryptedWallet"`
|
||||||
Sequence wallet.Sequence `json:"sequence"`
|
Sequence wallet.Sequence `json:"sequence"`
|
||||||
Hmac wallet.WalletHmac `json:"hmac"`
|
Hmac wallet.WalletHmac `json:"hmac"`
|
||||||
Error string `json:"error"` // in case of 409 Conflict responses. TODO - make field not show up if it's empty, to avoid confusion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleWallet(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) handleWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -103,6 +102,11 @@ func (s *Server) getWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
fmt.Fprintf(w, string(response))
|
fmt.Fprintf(w, string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Response Code:
|
||||||
|
// 200: Update successful
|
||||||
|
// 409: Update unsuccessful due to new wallet's sequence not being 1 +
|
||||||
|
// current wallet's sequence
|
||||||
|
// 500: Update unsuccessful for unanticipated reasons
|
||||||
func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
var walletRequest WalletRequest
|
var walletRequest WalletRequest
|
||||||
if !getPostData(w, req, &walletRequest) {
|
if !getPostData(w, req, &walletRequest) {
|
||||||
|
@ -114,21 +118,10 @@ func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
latestEncryptedWallet, latestSequence, latestHmac, sequenceCorrect, err := s.store.SetWallet(
|
err := s.store.SetWallet(authToken.UserId, walletRequest.EncryptedWallet, walletRequest.Sequence, walletRequest.Hmac)
|
||||||
authToken.UserId,
|
|
||||||
walletRequest.EncryptedWallet,
|
|
||||||
walletRequest.Sequence,
|
|
||||||
walletRequest.Hmac,
|
|
||||||
)
|
|
||||||
|
|
||||||
var response []byte
|
if err == store.ErrWrongSequence {
|
||||||
|
errorJson(w, http.StatusConflict, "Bad sequence number")
|
||||||
if err == store.ErrNoWallet {
|
|
||||||
// We failed to update, and when we tried pulling the latest wallet,
|
|
||||||
// there was nothing there. This should only happen if the client sets
|
|
||||||
// sequence != 1 for the first wallet, which would be a bug.
|
|
||||||
// TODO - figure out better error messages and/or document this
|
|
||||||
errorJson(w, http.StatusConflict, "Bad sequence number (No existing wallet)")
|
|
||||||
return
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
// Something other than sequence error
|
// Something other than sequence error
|
||||||
|
@ -136,16 +129,8 @@ func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
walletResponse := WalletResponse{
|
var response []byte
|
||||||
EncryptedWallet: latestEncryptedWallet,
|
var walletResponse struct{}
|
||||||
Sequence: latestSequence,
|
|
||||||
Hmac: latestHmac,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !sequenceCorrect {
|
|
||||||
// TODO - should we even call this an error?
|
|
||||||
walletResponse.Error = http.StatusText(http.StatusConflict) + ": " + "Bad sequence number"
|
|
||||||
}
|
|
||||||
response, err = json.Marshal(walletResponse)
|
response, err = json.Marshal(walletResponse)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -153,18 +138,5 @@ func (s *Server) postWallet(w http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response Code:
|
fmt.Fprintf(w, string(response))
|
||||||
// 200: Update successful
|
|
||||||
// 409: Update unsuccessful, probably due to new wallet's
|
|
||||||
// sequence not being 1 + current wallet's sequence
|
|
||||||
//
|
|
||||||
// Response Body:
|
|
||||||
// Current wallet (if it exists). If update successful, we just return
|
|
||||||
// the same one passed in. If update not successful, return the latest one
|
|
||||||
// from the db for the client to merge.
|
|
||||||
if sequenceCorrect {
|
|
||||||
fmt.Fprintf(w, string(response))
|
|
||||||
} else {
|
|
||||||
http.Error(w, string(response), http.StatusConflict)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,10 +131,7 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
|
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
expectedErrorString string
|
expectedErrorString string
|
||||||
|
expectSetWalletCall bool
|
||||||
// There is one case where we expect both the error field and the normal
|
|
||||||
// body fields. So, this needs to be separate.
|
|
||||||
expectWalletBody bool
|
|
||||||
|
|
||||||
// This is getting messy, but in the case of validation failures, we don't
|
// This is getting messy, but in the case of validation failures, we don't
|
||||||
// even get around to trying to get an auth token, since the token string is
|
// even get around to trying to get an auth token, since the token string is
|
||||||
|
@ -144,93 +141,38 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
|
|
||||||
// `new...` refers to what is being passed into the via POST request (and
|
// `new...` refers to what is being passed into the via POST request (and
|
||||||
// what gets passed into SetWallet for the *non-error* cases below)
|
// what gets passed into SetWallet for the *non-error* cases below)
|
||||||
// `returned...` refers to what the SetWallet function returns (and what
|
newEncryptedWallet wallet.EncryptedWallet
|
||||||
// gets returned in the request response for the *non-error* cases below)
|
newSequence wallet.Sequence
|
||||||
newEncryptedWallet wallet.EncryptedWallet
|
newHmac wallet.WalletHmac
|
||||||
returnedEncryptedWallet wallet.EncryptedWallet
|
|
||||||
newSequence wallet.Sequence
|
|
||||||
returnedSequence wallet.Sequence
|
|
||||||
newHmac wallet.WalletHmac
|
|
||||||
returnedHmac wallet.WalletHmac
|
|
||||||
|
|
||||||
// Passed-in sequence was correct. No conflict.
|
|
||||||
sequenceCorrect bool
|
|
||||||
|
|
||||||
storeErrors TestStoreFunctionsErrors
|
storeErrors TestStoreFunctionsErrors
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "success",
|
name: "success",
|
||||||
expectedStatusCode: http.StatusOK,
|
expectedStatusCode: http.StatusOK,
|
||||||
expectWalletBody: true,
|
expectSetWalletCall: true,
|
||||||
|
|
||||||
// Simulates a situation where the existing sequence is 1, the new
|
// Simulates a situation where the existing sequence is 1, the new
|
||||||
// sequence is 2. We don't see the existing data in this case because it
|
// sequence is 2.
|
||||||
// successfully updates to and returns the new data. New and returned are
|
|
||||||
// the same here.
|
|
||||||
|
|
||||||
sequenceCorrect: true,
|
|
||||||
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
|
||||||
newSequence: wallet.Sequence(2),
|
|
||||||
returnedSequence: wallet.Sequence(2),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
returnedHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "conflict",
|
|
||||||
expectedStatusCode: http.StatusConflict,
|
|
||||||
// In the special case of "conflict" where there *is* a stored wallet, we
|
|
||||||
// process the function in a normal way, but we still have the Error field.
|
|
||||||
// So, we can't rely on `tc.expectedErrorString == ""` to indicate that it
|
|
||||||
// is the type of error without a body. So, we need to specify this
|
|
||||||
// separately. In this case we will check the error string along with the
|
|
||||||
// body.
|
|
||||||
expectWalletBody: true,
|
|
||||||
|
|
||||||
// Simulates a situation where the existing sequence is 3, the new sequence
|
|
||||||
// is 2. This is a conflict, because the new sequence should be 4. A new
|
|
||||||
// sequence of 3 would also be a conflict, but we want two different
|
|
||||||
// sequence numbers for a clear test. We return the existing data in this
|
|
||||||
// case for the client to merge in. Note that we're passing in a sequence
|
|
||||||
// that makes sense for a conflict case, the actual behavior is triggered by
|
|
||||||
// sequenceCorrect=false
|
|
||||||
|
|
||||||
|
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
||||||
|
newSequence: wallet.Sequence(2),
|
||||||
|
newHmac: wallet.WalletHmac("my-hmac"),
|
||||||
|
}, {
|
||||||
|
name: "conflict",
|
||||||
|
expectedStatusCode: http.StatusConflict,
|
||||||
expectedErrorString: http.StatusText(http.StatusConflict) + ": Bad sequence number",
|
expectedErrorString: http.StatusText(http.StatusConflict) + ": Bad sequence number",
|
||||||
|
expectSetWalletCall: true,
|
||||||
|
|
||||||
sequenceCorrect: false,
|
// Simulates a situation where the existing sequence is *not* 1, the new
|
||||||
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-new"),
|
// proposed sequence is 2, and it thus fails with a conflict.
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-existing"),
|
|
||||||
newSequence: wallet.Sequence(2),
|
|
||||||
returnedSequence: wallet.Sequence(3),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac-new"),
|
|
||||||
returnedHmac: wallet.WalletHmac("my-hmac-existing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "conflict with no stored wallet",
|
|
||||||
expectedStatusCode: http.StatusConflict,
|
|
||||||
|
|
||||||
// Simulates a situation where there is no stored wallet. In such a case the
|
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet-new"),
|
||||||
// correct sequence would be 1, which implies the wallet should be inserted
|
newSequence: wallet.Sequence(2),
|
||||||
// (as opposed to updated). We will be passing in a sequence of 2 for
|
newHmac: wallet.WalletHmac("my-hmac-new"),
|
||||||
// clarity, but what will actually trigger the desired error we are testing
|
|
||||||
// for is SetWallet returning ErrNoWallet, which is what the store is
|
|
||||||
// expected to return in this situation.
|
|
||||||
|
|
||||||
expectedErrorString: http.StatusText(http.StatusConflict) + ": Bad sequence number (No existing wallet)",
|
storeErrors: TestStoreFunctionsErrors{SetWallet: store.ErrWrongSequence},
|
||||||
|
}, {
|
||||||
// In this case the "returned" values are blank because there will be
|
|
||||||
// nothing to return
|
|
||||||
sequenceCorrect: false,
|
|
||||||
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet(""),
|
|
||||||
newSequence: wallet.Sequence(2),
|
|
||||||
returnedSequence: wallet.Sequence(0),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
returnedHmac: wallet.WalletHmac(""),
|
|
||||||
|
|
||||||
storeErrors: TestStoreFunctionsErrors{SetWallet: store.ErrNoWallet},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "validation error",
|
name: "validation error",
|
||||||
expectedStatusCode: http.StatusBadRequest,
|
expectedStatusCode: http.StatusBadRequest,
|
||||||
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation",
|
expectedErrorString: http.StatusText(http.StatusBadRequest) + ": Request failed validation",
|
||||||
|
@ -240,47 +182,33 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
// validate function is called. We'll check the rest of the validation
|
// validate function is called. We'll check the rest of the validation
|
||||||
// errors in the other test below.
|
// errors in the other test below.
|
||||||
|
|
||||||
sequenceCorrect: true,
|
newEncryptedWallet: wallet.EncryptedWallet(""),
|
||||||
newEncryptedWallet: wallet.EncryptedWallet(""),
|
newSequence: wallet.Sequence(2),
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
newHmac: wallet.WalletHmac("my-hmac"),
|
||||||
newSequence: wallet.Sequence(2),
|
}, {
|
||||||
returnedSequence: wallet.Sequence(2),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
returnedHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: "auth error",
|
name: "auth error",
|
||||||
expectedStatusCode: http.StatusUnauthorized,
|
expectedStatusCode: http.StatusUnauthorized,
|
||||||
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": Token Not Found",
|
expectedErrorString: http.StatusText(http.StatusUnauthorized) + ": Token Not Found",
|
||||||
|
|
||||||
// Putting in valid data here so it's clear that this isn't what causes
|
// Putting in valid data here so it's clear that this isn't what causes
|
||||||
// the error
|
// the error
|
||||||
sequenceCorrect: true,
|
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
||||||
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
newSequence: wallet.Sequence(2),
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
newHmac: wallet.WalletHmac("my-hmac"),
|
||||||
newSequence: wallet.Sequence(2),
|
|
||||||
returnedSequence: wallet.Sequence(2),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
returnedHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
|
|
||||||
// What causes the error
|
// What causes the error
|
||||||
storeErrors: TestStoreFunctionsErrors{GetToken: store.ErrNoToken},
|
storeErrors: TestStoreFunctionsErrors{GetToken: store.ErrNoToken},
|
||||||
},
|
}, {
|
||||||
{
|
name: "db error setting wallet",
|
||||||
name: "db error setting or getting wallet",
|
|
||||||
expectedStatusCode: http.StatusInternalServerError,
|
expectedStatusCode: http.StatusInternalServerError,
|
||||||
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
expectedErrorString: http.StatusText(http.StatusInternalServerError),
|
||||||
|
expectSetWalletCall: true,
|
||||||
|
|
||||||
// Putting in valid data here so it's clear that this isn't what causes
|
// Putting in valid data here so it's clear that this isn't what causes
|
||||||
// the error
|
// the error
|
||||||
sequenceCorrect: true,
|
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
||||||
newEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
newSequence: wallet.Sequence(2),
|
||||||
returnedEncryptedWallet: wallet.EncryptedWallet("my-encrypted-wallet"),
|
newHmac: wallet.WalletHmac("my-hmac"),
|
||||||
newSequence: wallet.Sequence(2),
|
|
||||||
returnedSequence: wallet.Sequence(2),
|
|
||||||
newHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
returnedHmac: wallet.WalletHmac("my-hmac"),
|
|
||||||
|
|
||||||
// What causes the error
|
// What causes the error
|
||||||
storeErrors: TestStoreFunctionsErrors{SetWallet: fmt.Errorf("Some random db problem")},
|
storeErrors: TestStoreFunctionsErrors{SetWallet: fmt.Errorf("Some random db problem")},
|
||||||
|
@ -301,11 +229,6 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
Scope: auth.ScopeFull,
|
Scope: auth.ScopeFull,
|
||||||
},
|
},
|
||||||
|
|
||||||
TestEncryptedWallet: tc.returnedEncryptedWallet,
|
|
||||||
TestSequence: tc.returnedSequence,
|
|
||||||
TestHmac: tc.returnedHmac,
|
|
||||||
TestSequenceCorrect: tc.sequenceCorrect,
|
|
||||||
|
|
||||||
Errors: tc.storeErrors,
|
Errors: tc.storeErrors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,21 +261,11 @@ func TestServerPostWallet(t *testing.T) {
|
||||||
expectStatusCode(t, w, tc.expectedStatusCode)
|
expectStatusCode(t, w, tc.expectedStatusCode)
|
||||||
expectErrorString(t, body, tc.expectedErrorString)
|
expectErrorString(t, body, tc.expectedErrorString)
|
||||||
|
|
||||||
if !tc.expectWalletBody {
|
if tc.expectedErrorString == "" && string(body) != "{}" {
|
||||||
return // The rest of the test does not apply
|
t.Errorf("Expected post wallet response to be \"{}\": result: %+v", string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
var result WalletResponse
|
if want, got := (SetWalletCall{tc.newEncryptedWallet, tc.newSequence, tc.newHmac}), testStore.Called.SetWallet; tc.expectSetWalletCall && want != got {
|
||||||
err := json.Unmarshal(body, &result)
|
|
||||||
|
|
||||||
if err != nil ||
|
|
||||||
result.EncryptedWallet != tc.returnedEncryptedWallet ||
|
|
||||||
result.Hmac != tc.returnedHmac ||
|
|
||||||
result.Sequence != tc.returnedSequence {
|
|
||||||
t.Errorf("Expected wallet response to have the test wallet values: result: %+v err: %+v", string(body), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if want, got := (SetWalletCall{tc.newEncryptedWallet, tc.newSequence, tc.newHmac}), testStore.Called.SetWallet; want != got {
|
|
||||||
t.Errorf("Store.SetWallet called with: expected %+v, got %+v", want, got)
|
t.Errorf("Store.SetWallet called with: expected %+v, got %+v", want, got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,6 +21,8 @@ var (
|
||||||
ErrDuplicateWallet = fmt.Errorf("Wallet already exists for this user")
|
ErrDuplicateWallet = fmt.Errorf("Wallet already exists for this user")
|
||||||
ErrNoWallet = fmt.Errorf("Wallet does not exist for this user at this sequence")
|
ErrNoWallet = fmt.Errorf("Wallet does not exist for this user at this sequence")
|
||||||
|
|
||||||
|
ErrWrongSequence = fmt.Errorf("Wallet could not be updated to this sequence")
|
||||||
|
|
||||||
ErrDuplicateEmail = fmt.Errorf("Email already exists for this user")
|
ErrDuplicateEmail = fmt.Errorf("Email already exists for this user")
|
||||||
ErrDuplicateAccount = fmt.Errorf("User already has an account")
|
ErrDuplicateAccount = fmt.Errorf("User already has an account")
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ var (
|
||||||
type StoreInterface interface {
|
type StoreInterface interface {
|
||||||
SaveToken(*auth.AuthToken) error
|
SaveToken(*auth.AuthToken) error
|
||||||
GetToken(auth.TokenString) (*auth.AuthToken, error)
|
GetToken(auth.TokenString) (*auth.AuthToken, error)
|
||||||
SetWallet(auth.UserId, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, bool, error)
|
SetWallet(auth.UserId, wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac) error
|
||||||
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
GetWallet(auth.UserId) (wallet.EncryptedWallet, wallet.Sequence, wallet.WalletHmac, error)
|
||||||
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
GetUserId(auth.Email, auth.Password) (auth.UserId, error)
|
||||||
CreateAccount(auth.Email, auth.Password) (err error)
|
CreateAccount(auth.Email, auth.Password) (err error)
|
||||||
|
@ -282,39 +284,17 @@ func (s *Store) updateWalletToSequence(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assumption: Sequence has been validated (>=1)
|
// Assumption: Sequence has been validated (>=1)
|
||||||
func (s *Store) SetWallet(
|
func (s *Store) SetWallet(userId auth.UserId, encryptedWallet wallet.EncryptedWallet, sequence wallet.Sequence, hmac wallet.WalletHmac) (err error) {
|
||||||
userId auth.UserId,
|
|
||||||
encryptedWallet wallet.EncryptedWallet,
|
|
||||||
sequence wallet.Sequence,
|
|
||||||
hmac wallet.WalletHmac,
|
|
||||||
// TODO `sequenceCorrect` should probably be replaced with `status`, that can
|
|
||||||
// equal `Updated` or `SequenceMismatch`. Maybe with a message for the API.
|
|
||||||
// Like an error, but not, because the function still returns a value.
|
|
||||||
// Right now, we have:
|
|
||||||
// `sequenceCorrect==true` and `err==nil` implies it updated.
|
|
||||||
// We could also have:
|
|
||||||
// `sequenceMismatch==true` or `err!=nil` implying it didn't update.
|
|
||||||
// Or:
|
|
||||||
// `updated==false` and `err=nil` implying the sequence mismatched.
|
|
||||||
// I don't like this implication stuff, the "status" should be explicit so
|
|
||||||
// we don't make bugs.
|
|
||||||
) (latestEncryptedWallet wallet.EncryptedWallet, latestSequence wallet.Sequence, latestHmac wallet.WalletHmac, sequenceCorrect bool, err error) {
|
|
||||||
if sequence == 1 {
|
if sequence == 1 {
|
||||||
// If sequence == 1, the client assumed that this is our first
|
// If sequence == 1, the client assumed that this is our first
|
||||||
// wallet. Try to insert. If we get a conflict, the client
|
// wallet. Try to insert. If we get a conflict, the client
|
||||||
// assumed incorrectly and we proceed below to return the latest
|
// assumed incorrectly and we proceed below to return the latest
|
||||||
// wallet from the db.
|
// wallet from the db.
|
||||||
err = s.insertFirstWallet(userId, encryptedWallet, hmac)
|
err = s.insertFirstWallet(userId, encryptedWallet, hmac)
|
||||||
if err == nil {
|
if err == ErrDuplicateWallet {
|
||||||
// Successful update
|
// A wallet already exists. That means the input sequence should not be 1.
|
||||||
latestEncryptedWallet = encryptedWallet
|
// To the caller, this means the sequence was wrong.
|
||||||
latestSequence = sequence
|
err = ErrWrongSequence
|
||||||
latestHmac = hmac
|
|
||||||
sequenceCorrect = true
|
|
||||||
return
|
|
||||||
} else if err != ErrDuplicateWallet {
|
|
||||||
// Unsuccessful update for reasons other than sequence conflict
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If sequence > 1, the client assumed that it is replacing wallet
|
// If sequence > 1, the client assumed that it is replacing wallet
|
||||||
|
@ -322,25 +302,12 @@ func (s *Store) SetWallet(
|
||||||
// sequence - 1. If we updated no rows, the client assumed incorrectly
|
// sequence - 1. If we updated no rows, the client assumed incorrectly
|
||||||
// and we proceed below to return the latest wallet from the db.
|
// and we proceed below to return the latest wallet from the db.
|
||||||
err = s.updateWalletToSequence(userId, encryptedWallet, sequence, hmac)
|
err = s.updateWalletToSequence(userId, encryptedWallet, sequence, hmac)
|
||||||
if err == nil {
|
if err == ErrNoWallet {
|
||||||
latestEncryptedWallet = encryptedWallet
|
// No wallet found to replace at the `sequence - 1`. To the caller, this
|
||||||
latestSequence = sequence
|
// means the sequence they put in was wrong.
|
||||||
latestHmac = hmac
|
err = ErrWrongSequence
|
||||||
sequenceCorrect = true
|
|
||||||
return
|
|
||||||
} else if err != ErrNoWallet {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We failed to update above due to a sequence conflict. Perhaps the client
|
|
||||||
// was unaware of an update done by another client. Let's send back the latest
|
|
||||||
// version right away so the requesting client can take care of it.
|
|
||||||
//
|
|
||||||
// Note that this means that `err` will not be `nil` at this point, but we
|
|
||||||
// already accounted for it with `sequenceCorrect=false`. Instead, we'll pass
|
|
||||||
// on any errors from calling `GetWallet`.
|
|
||||||
latestEncryptedWallet, latestSequence, latestHmac, err = s.GetWallet(userId)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,36 +25,34 @@ Now that the account exists, grab an auth token with both clients.
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> c1.get_auth_token()
|
>>> c1.get_auth_token()
|
||||||
Got auth token: 2e1c00c0f2f205defc177bd21e64dd01c669e234cf23bbc19f73e720ac1ef12d
|
Got auth token: 4a3d9b8569c3b06079ff26d60ebc56db6254305217602c19b0af6e02db6d95d7
|
||||||
>>> c2.get_auth_token()
|
>>> c2.get_auth_token()
|
||||||
Got auth token: 07ab32cfac3961d30570537d4082abdf08123de6e5a28670dbf680be45e442d5
|
Got auth token: 33fd77031ccaec966018867e960446bf39d51a3c492c3d997d5f1aa13c75298d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Syncing
|
## Syncing
|
||||||
|
|
||||||
Create a new wallet + metadata (we'll wrap it in a struct we'll call `WalletState` in this client) using `init_wallet_state` and POST them to the server. The metadata (as of now) in the walletstate is only `sequence`. `sequence` is an integer that increments for every POSTed wallet. This is bookkeeping to prevent certain syncing errors.
|
Create a new wallet + metadata (we'll wrap it in a struct we'll call `WalletState` in this client) using `init_wallet_state` and POST them to the server. The metadata (as of now) in the walletstate is only `sequence`. `sequence` is an integer that increments for every POSTed wallet. This is bookkeeping to prevent certain syncing errors.
|
||||||
|
|
||||||
_Note that after POSTing, it says it "got" a new wallet. This is because the POST endpoint also returns the latest version. The purpose of this will be explained in "Conflicts" below._
|
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> c1.init_wallet_state()
|
>>> c1.init_wallet_state()
|
||||||
Wallet not found
|
|
||||||
No wallet found on the server for this account. Starting a new one.
|
|
||||||
>>> c1.update_remote_wallet()
|
>>> c1.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=1, encrypted_wallet='czo4MTkyOjE2OjE6CjZHlCv4ZyHiPKA7PoIOOkQ6Fh9fYUYPe9xwiZRYdLKHDgtEQCIcwkNldP1TN8TwTht4Qj5QEnApwQkd2Y20nVjdCUTKLzu4gmdP8QBz2EEGR+XmZgosX937E8bmmqgC55ttgt8fh0o62cTonF4h1LLI7DoWw1SvEcqIIAEn/dc=')
|
WalletState(sequence=1, encrypted_wallet='czo4MTkyOjE2OjE6/MNVSMrjIqPzrD/oaub++J3lc5qW+baxD0EI6n5/XqGgRsUND3G7fqRsn/riULM4zap+jI8XgW6l1rieJWGZXPQvIZJP8B7gQvBDfzlY0BxUgECeX38I5EtRFNWU3sTwmAaAaDuBpaBXvnf2hu4SEp5xl/OQVg9h+BluTZBdLSU=')
|
||||||
'Success'
|
'Success'
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, call `init_wallet_state` with the other client. This time, `init_wallet_state` will GET the wallet from the server. In general, `init_wallet_state` is used to set up a new client; first it checks the server, then failing that, it initializes it locally. (In a real client, it would save the walletstate to disk, and `init_wallet_state` would check there before checking the server).
|
Now, call `init_wallet_state` with the other client. Then, we call `get_remote_wallet` to GET the wallet from the server. (In a real client, it would also save the walletstate to disk, and `init_wallet_state` would check there before checking the server).
|
||||||
|
|
||||||
(There are a few potential unresolved issues surrounding this related to sequence of events. Check comments on `init_wallet_state`. SDK again works around them with the timestamps.)
|
(There are a few potential unresolved issues surrounding this related to sequence of events. Check comments on `init_wallet_state`. SDK again works around them with the timestamps.)
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> c2.init_wallet_state()
|
>>> c2.init_wallet_state()
|
||||||
Got latest walletState:
|
>>> c2.get_remote_wallet()
|
||||||
WalletState(sequence=1, encrypted_wallet='czo4MTkyOjE2OjE6CjZHlCv4ZyHiPKA7PoIOOkQ6Fh9fYUYPe9xwiZRYdLKHDgtEQCIcwkNldP1TN8TwTht4Qj5QEnApwQkd2Y20nVjdCUTKLzu4gmdP8QBz2EEGR+XmZgosX937E8bmmqgC55ttgt8fh0o62cTonF4h1LLI7DoWw1SvEcqIIAEn/dc=')
|
Got (and maybe merged in) latest walletState:
|
||||||
|
WalletState(sequence=1, encrypted_wallet='czo4MTkyOjE2OjE6/MNVSMrjIqPzrD/oaub++J3lc5qW+baxD0EI6n5/XqGgRsUND3G7fqRsn/riULM4zap+jI8XgW6l1rieJWGZXPQvIZJP8B7gQvBDfzlY0BxUgECeX38I5EtRFNWU3sTwmAaAaDuBpaBXvnf2hu4SEp5xl/OQVg9h+BluTZBdLSU=')
|
||||||
|
'Success'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
@ -64,12 +62,12 @@ Push a new version, GET it with the other client. Even though we haven't edited
|
||||||
```
|
```
|
||||||
>>> c2.update_remote_wallet()
|
>>> c2.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=2, encrypted_wallet='czo4MTkyOjE2OjE6LsWo7O3EQVw+buxGPuqJBBEn3oBM3/sAII2NjpbKi7tEvWxbWmKb+nNyr3fuvQ6YZZda0i0Rb7Veuq7ym+hYAn2pTt/8WrYR8K1HFnxs3y1m91HQIsXrl6NwxU5t+mZ6uInQUfEGEV6JLHfbt1NJ2pYlYvYTelusKZXq/kja8i4=')
|
WalletState(sequence=2, encrypted_wallet='czo4MTkyOjE2OjE6MIPxgbxNGbaZWboH6ci6wBT3izdpb/B3JYdl3nJdQn6EV54W4QaYUvuUxMa5XngiXlNLcLbmFRqeYj/mgAbEVXRKLyLQxjB7rIhGcRxsHbzGR8YDMVvP+m5dWaxevlZc7cEZkpRQKfFyuc+pnjPEk9SUvEgioN1Hxir6DonMqlA=')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c1.get_remote_wallet()
|
>>> c1.get_remote_wallet()
|
||||||
Got latest walletState:
|
Got (and maybe merged in) latest walletState:
|
||||||
WalletState(sequence=2, encrypted_wallet='czo4MTkyOjE2OjE6LsWo7O3EQVw+buxGPuqJBBEn3oBM3/sAII2NjpbKi7tEvWxbWmKb+nNyr3fuvQ6YZZda0i0Rb7Veuq7ym+hYAn2pTt/8WrYR8K1HFnxs3y1m91HQIsXrl6NwxU5t+mZ6uInQUfEGEV6JLHfbt1NJ2pYlYvYTelusKZXq/kja8i4=')
|
WalletState(sequence=2, encrypted_wallet='czo4MTkyOjE2OjE6MIPxgbxNGbaZWboH6ci6wBT3izdpb/B3JYdl3nJdQn6EV54W4QaYUvuUxMa5XngiXlNLcLbmFRqeYj/mgAbEVXRKLyLQxjB7rIhGcRxsHbzGR8YDMVvP+m5dWaxevlZc7cEZkpRQKfFyuc+pnjPEk9SUvEgioN1Hxir6DonMqlA=')
|
||||||
'Success'
|
'Success'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -94,12 +92,12 @@ The wallet is synced between the clients. The client with the changed preference
|
||||||
```
|
```
|
||||||
>>> c1.update_remote_wallet()
|
>>> c1.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=3, encrypted_wallet='czo4MTkyOjE2OjE6l5SVvs2yNDoC5j1316n0xQ5H6K1UEso/JpdpShfLW2bCY3lg9vOcwayO1v085RyItxEwtrtSnD3fnan3kr86GmSI8U6x5DxASHVdgceLBrclVkuCpFXllz6YNtWo5thjbf63PWSg4k6LHI8w50BT2tu9FUufCi67n7sTWnGb/0AjAFYU1sUTJ9aoeiZYrrur')
|
WalletState(sequence=3, encrypted_wallet='czo4MTkyOjE2OjE6YUEKfjxhUXeHrNbPuWpMt5o/6H5fSSKFZAMkb8YugMGEHzVAZDfGMdowwdycXkyTZtPRiMSs+kgOX8BLomcz/I+de8b1EsXribYR05sgySRJiPoW8VBRlmgbRapZ9iGaxvgJJWmVAO42beNWtnuE3bdpDtWtZjgcXWq6lnhNlETmKEEPthezGB8svHPHt/rJ')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c2.get_remote_wallet()
|
>>> c2.get_remote_wallet()
|
||||||
Got latest walletState:
|
Got (and maybe merged in) latest walletState:
|
||||||
WalletState(sequence=3, encrypted_wallet='czo4MTkyOjE2OjE6l5SVvs2yNDoC5j1316n0xQ5H6K1UEso/JpdpShfLW2bCY3lg9vOcwayO1v085RyItxEwtrtSnD3fnan3kr86GmSI8U6x5DxASHVdgceLBrclVkuCpFXllz6YNtWo5thjbf63PWSg4k6LHI8w50BT2tu9FUufCi67n7sTWnGb/0AjAFYU1sUTJ9aoeiZYrrur')
|
WalletState(sequence=3, encrypted_wallet='czo4MTkyOjE2OjE6YUEKfjxhUXeHrNbPuWpMt5o/6H5fSSKFZAMkb8YugMGEHzVAZDfGMdowwdycXkyTZtPRiMSs+kgOX8BLomcz/I+de8b1EsXribYR05sgySRJiPoW8VBRlmgbRapZ9iGaxvgJJWmVAO42beNWtnuE3bdpDtWtZjgcXWq6lnhNlETmKEEPthezGB8svHPHt/rJ')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c2.get_preferences()
|
>>> c2.get_preferences()
|
||||||
{'animal': 'cow', 'car': ''}
|
{'animal': 'cow', 'car': ''}
|
||||||
|
@ -125,8 +123,8 @@ One client POSTs its change first.
|
||||||
```
|
```
|
||||||
>>> c1.update_remote_wallet()
|
>>> c1.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=4, encrypted_wallet='czo4MTkyOjE2OjE66nridrsrXcL/DlcudUs7RaAIZ3jRYQJhaacRH3vPNx0TZqkJbDcjMiHbiHY6U2AVhoAsLPIf/zcU+uDTw4IRcOL9Gozupc8tCrIcgm/kwXjnQI9RNzIfDsFxalBKj0u7Xf0c+5f/ntr4Hs9Q/Y7qthseNbUBZKU12KxNlmDcE7knLOui6NQdsUvFpuI/Rzgr')
|
WalletState(sequence=4, encrypted_wallet='czo4MTkyOjE2OjE6ZAO02VSfc0UTNcKJosuTzdpB1GCRw+f1bCrR/1aFDGoK5Iq/OyKXygp3p2trj2EU1SUfp6m/FiWYdN920uzpaQnIbOlEs6anPpd3alNQmNfuT1s8bKnliO6so657VjZf0QdadDrCVa8WZMiuHY+wP2H5LpzDIrRYrzNyyUuhffbh8yk8cQhgRScFKczpAnu+')
|
||||||
'Success'
|
'Success'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -136,8 +134,8 @@ Eventually, the client will be responsible (or at least more responsible) for me
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> c2.get_remote_wallet()
|
>>> c2.get_remote_wallet()
|
||||||
Got latest walletState:
|
Got (and maybe merged in) latest walletState:
|
||||||
WalletState(sequence=4, encrypted_wallet='czo4MTkyOjE2OjE66nridrsrXcL/DlcudUs7RaAIZ3jRYQJhaacRH3vPNx0TZqkJbDcjMiHbiHY6U2AVhoAsLPIf/zcU+uDTw4IRcOL9Gozupc8tCrIcgm/kwXjnQI9RNzIfDsFxalBKj0u7Xf0c+5f/ntr4Hs9Q/Y7qthseNbUBZKU12KxNlmDcE7knLOui6NQdsUvFpuI/Rzgr')
|
WalletState(sequence=4, encrypted_wallet='czo4MTkyOjE2OjE6ZAO02VSfc0UTNcKJosuTzdpB1GCRw+f1bCrR/1aFDGoK5Iq/OyKXygp3p2trj2EU1SUfp6m/FiWYdN920uzpaQnIbOlEs6anPpd3alNQmNfuT1s8bKnliO6so657VjZf0QdadDrCVa8WZMiuHY+wP2H5LpzDIrRYrzNyyUuhffbh8yk8cQhgRScFKczpAnu+')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c2.get_preferences()
|
>>> c2.get_preferences()
|
||||||
{'animal': 'horse', 'car': 'Audi'}
|
{'animal': 'horse', 'car': 'Audi'}
|
||||||
|
@ -148,12 +146,12 @@ Finally, the client with the merged wallet pushes it to the server, and the othe
|
||||||
```
|
```
|
||||||
>>> c2.update_remote_wallet()
|
>>> c2.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=5, encrypted_wallet='czo4MTkyOjE2OjE68NAahtUE4gg2M6Fam/E3brb4sv1TzcXJLvGRh4CY4416haOF1lxmKSdrvIPpOBvpNPS0B5qCbmpaKQ8Pm/WRCLj1yYUDVKgSZx0ru7AJBHiBLtpKA99Ia7XlWl129p6WtjJkbOoW8Ya+PEii72g4nrtM+j40Xe9UbVI463tlKYaRvmKr/BcoFGMJSB10Whh8')
|
WalletState(sequence=5, encrypted_wallet='czo4MTkyOjE2OjE6cat6gX80ib+t6bX9QlBw3jspj4jJ6U8AGULRDPNa8PbL4CX6ohZoXkt+duNYPxWDdyl8xqhwisWXTXkuGUBwP2zrVmZC3TNt5A9Pk/y/tNgMz50CY3JmNYcbCeZyoY+uV+cMfdO+n3p3hYriNKgn539NC6ug80U/2heevVax4NgMAF0lWEBM2E886+KkvfHG')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c1.get_remote_wallet()
|
>>> c1.get_remote_wallet()
|
||||||
Got latest walletState:
|
Got (and maybe merged in) latest walletState:
|
||||||
WalletState(sequence=5, encrypted_wallet='czo4MTkyOjE2OjE68NAahtUE4gg2M6Fam/E3brb4sv1TzcXJLvGRh4CY4416haOF1lxmKSdrvIPpOBvpNPS0B5qCbmpaKQ8Pm/WRCLj1yYUDVKgSZx0ru7AJBHiBLtpKA99Ia7XlWl129p6WtjJkbOoW8Ya+PEii72g4nrtM+j40Xe9UbVI463tlKYaRvmKr/BcoFGMJSB10Whh8')
|
WalletState(sequence=5, encrypted_wallet='czo4MTkyOjE2OjE6cat6gX80ib+t6bX9QlBw3jspj4jJ6U8AGULRDPNa8PbL4CX6ohZoXkt+duNYPxWDdyl8xqhwisWXTXkuGUBwP2zrVmZC3TNt5A9Pk/y/tNgMz50CY3JmNYcbCeZyoY+uV+cMfdO+n3p3hYriNKgn539NC6ug80U/2heevVax4NgMAF0lWEBM2E886+KkvfHG')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c1.get_preferences()
|
>>> c1.get_preferences()
|
||||||
{'animal': 'horse', 'car': 'Audi'}
|
{'animal': 'horse', 'car': 'Audi'}
|
||||||
|
@ -178,29 +176,34 @@ So for example, let's say we create diverging changes in the wallets:
|
||||||
{'animal': 'horse', 'car': 'Toyota'}
|
{'animal': 'horse', 'car': 'Toyota'}
|
||||||
```
|
```
|
||||||
|
|
||||||
We try to POST both of them to the server, but the second one fails because of the conflict. Instead, merges the two locally:
|
We try to POST both of them to the server. The second one fails because of the conflict, and we see that its preferences don't change yet.
|
||||||
|
|
||||||
```
|
```
|
||||||
>>> c2.update_remote_wallet()
|
>>> c2.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=6, encrypted_wallet='czo4MTkyOjE2OjE6sh95Bt0OfcDY3QwUWaPgPWD1WPYCkN2yg1+XLD/5puONhNyjzVAnhINqVvPy52pxfkVgkIScLacMQFq4W19d+SC5LConu+fPchBzYj14Wvc3/IEQiQIxbmkv6N9USvYsjAzjGqK7szistRJY4MHC4/wRbWRprfIE7BFcDaisFSe18mRs8D2KlhEzjNJu+X8+')
|
WalletState(sequence=6, encrypted_wallet='czo4MTkyOjE2OjE6IQ+uyjKiGAIEjoNliOsANoq2h/exQpwordUQFVbbHVhj27UbJS7ykMV4or5avEwNo+aCYC8j7HEqqaPnhvNYeeyPbmpfZS0lU7MXBehoqvIPR3GyTLM002t7SUrB+KxdvUX8RAamjiahDI8OeTOBmYhgQLSZt/ZDtRL/3f5l1JgLCjEbVKJY6Pim0hk7AlpK')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c1.update_remote_wallet()
|
>>> c1.update_remote_wallet()
|
||||||
Wallet state out of date. Getting updated wallet state. Try posting again after this.
|
Submitted wallet is out of date.
|
||||||
Got new walletState:
|
Could not update. Need to get new wallet and merge
|
||||||
WalletState(sequence=6, encrypted_wallet='czo4MTkyOjE2OjE6sh95Bt0OfcDY3QwUWaPgPWD1WPYCkN2yg1+XLD/5puONhNyjzVAnhINqVvPy52pxfkVgkIScLacMQFq4W19d+SC5LConu+fPchBzYj14Wvc3/IEQiQIxbmkv6N9USvYsjAzjGqK7szistRJY4MHC4/wRbWRprfIE7BFcDaisFSe18mRs8D2KlhEzjNJu+X8+')
|
'Failure'
|
||||||
|
>>> c1.get_preferences()
|
||||||
|
{'animal': 'horse', 'car': 'Toyota'}
|
||||||
|
```
|
||||||
|
|
||||||
|
The client that is out of date will then call `get_remote_wallet`, which GETs and automatically merges in the latest wallet. We see the preferences are now merged. Now it can make a second POST request containing the merged wallet.
|
||||||
|
|
||||||
|
```
|
||||||
|
>>> c1.get_remote_wallet()
|
||||||
|
Got (and maybe merged in) latest walletState:
|
||||||
|
WalletState(sequence=6, encrypted_wallet='czo4MTkyOjE2OjE6IQ+uyjKiGAIEjoNliOsANoq2h/exQpwordUQFVbbHVhj27UbJS7ykMV4or5avEwNo+aCYC8j7HEqqaPnhvNYeeyPbmpfZS0lU7MXBehoqvIPR3GyTLM002t7SUrB+KxdvUX8RAamjiahDI8OeTOBmYhgQLSZt/ZDtRL/3f5l1JgLCjEbVKJY6Pim0hk7AlpK')
|
||||||
'Success'
|
'Success'
|
||||||
>>> c1.get_preferences()
|
>>> c1.get_preferences()
|
||||||
{'animal': 'beaver', 'car': 'Toyota'}
|
{'animal': 'beaver', 'car': 'Toyota'}
|
||||||
```
|
|
||||||
|
|
||||||
Now that the merge is complete, the client can make a second POST request containing the merged wallet.
|
|
||||||
|
|
||||||
```
|
|
||||||
>>> c1.update_remote_wallet()
|
>>> c1.update_remote_wallet()
|
||||||
Successfully updated wallet state on server
|
Successfully updated wallet state on server
|
||||||
Got new walletState:
|
Synced walletState:
|
||||||
WalletState(sequence=7, encrypted_wallet='czo4MTkyOjE2OjE68J19IGGfoiRDm15Nb1sTj5yP9Mc3jpAeYarh206kLXKMKLKCIahmhLDMqBCXgwDe098uaIqB6IwKDfXbCVJHhWfqzu/5GoWPK1QZjhCu0rGxteFv4Tio0IYGg8CUYCvOhpQA319SXEf4sF9cyC32VwlL6qkJ2TzWTu9bTGUfZRGV3q9Rt9oL4OQHxuNIPEiE')
|
WalletState(sequence=7, encrypted_wallet='czo4MTkyOjE2OjE63OwBCfczOA+n0EMe0lHPwVvmrXsJwKJXGPYFSmdDseHbd3HRpOZ/Id5WeOuata5/dHJ4vdaaw8RNfpgR4KVzOkM5BUZNxzBaVf/BEYL8nJcbv7l5ZLs6Q15IqvlmZ3HBPVzxO/WYqm4aL9+CNeoYG2LzaIxsnzf31ZoG9I78B6wxK5JXCjDS+nuh/4NM+REE')
|
||||||
'Success'
|
'Success'
|
||||||
```
|
```
|
||||||
|
|
|
@ -67,8 +67,6 @@ print("""
|
||||||
## Syncing
|
## Syncing
|
||||||
|
|
||||||
Create a new wallet + metadata (we'll wrap it in a struct we'll call `WalletState` in this client) using `init_wallet_state` and POST them to the server. The metadata (as of now) in the walletstate is only `sequence`. `sequence` is an integer that increments for every POSTed wallet. This is bookkeeping to prevent certain syncing errors.
|
Create a new wallet + metadata (we'll wrap it in a struct we'll call `WalletState` in this client) using `init_wallet_state` and POST them to the server. The metadata (as of now) in the walletstate is only `sequence`. `sequence` is an integer that increments for every POSTed wallet. This is bookkeeping to prevent certain syncing errors.
|
||||||
|
|
||||||
_Note that after POSTing, it says it "got" a new wallet. This is because the POST endpoint also returns the latest version. The purpose of this will be explained in "Conflicts" below._
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
code_block("""
|
code_block("""
|
||||||
|
@ -77,13 +75,14 @@ c1.update_remote_wallet()
|
||||||
""")
|
""")
|
||||||
|
|
||||||
print("""
|
print("""
|
||||||
Now, call `init_wallet_state` with the other client. This time, `init_wallet_state` will GET the wallet from the server. In general, `init_wallet_state` is used to set up a new client; first it checks the server, then failing that, it initializes it locally. (In a real client, it would save the walletstate to disk, and `init_wallet_state` would check there before checking the server).
|
Now, call `init_wallet_state` with the other client. Then, we call `get_remote_wallet` to GET the wallet from the server. (In a real client, it would also save the walletstate to disk, and `init_wallet_state` would check there before checking the server).
|
||||||
|
|
||||||
(There are a few potential unresolved issues surrounding this related to sequence of events. Check comments on `init_wallet_state`. SDK again works around them with the timestamps.)
|
(There are a few potential unresolved issues surrounding this related to sequence of events. Check comments on `init_wallet_state`. SDK again works around them with the timestamps.)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
code_block("""
|
code_block("""
|
||||||
c2.init_wallet_state()
|
c2.init_wallet_state()
|
||||||
|
c2.get_remote_wallet()
|
||||||
""")
|
""")
|
||||||
|
|
||||||
print("""
|
print("""
|
||||||
|
@ -183,7 +182,7 @@ c1.get_preferences()
|
||||||
""")
|
""")
|
||||||
|
|
||||||
print("""
|
print("""
|
||||||
We try to POST both of them to the server, but the second one fails because of the conflict. Instead, merges the two locally:
|
We try to POST both of them to the server. The second one fails because of the conflict, and we see that its preferences don't change yet.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
code_block("""
|
code_block("""
|
||||||
|
@ -193,9 +192,11 @@ c1.get_preferences()
|
||||||
""")
|
""")
|
||||||
|
|
||||||
print("""
|
print("""
|
||||||
Now that the merge is complete, the client can make a second POST request containing the merged wallet.
|
The client that is out of date will then call `get_remote_wallet`, which GETs and automatically merges in the latest wallet. We see the preferences are now merged. Now it can make a second POST request containing the merged wallet.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
code_block("""
|
code_block("""
|
||||||
|
c1.get_remote_wallet()
|
||||||
|
c1.get_preferences()
|
||||||
c1.update_remote_wallet()
|
c1.update_remote_wallet()
|
||||||
""")
|
""")
|
||||||
|
|
|
@ -58,7 +58,7 @@ class LBRYSDK():
|
||||||
|
|
||||||
class WalletSync():
|
class WalletSync():
|
||||||
def __init__(self, local):
|
def __init__(self, local):
|
||||||
self.API_VERSION = 1
|
self.API_VERSION = 2
|
||||||
|
|
||||||
if local:
|
if local:
|
||||||
BASE_URL = 'http://localhost:8090'
|
BASE_URL = 'http://localhost:8090'
|
||||||
|
@ -131,24 +131,16 @@ class WalletSync():
|
||||||
response = requests.post(self.WALLET_URL, body)
|
response = requests.post(self.WALLET_URL, body)
|
||||||
|
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
conflict = False
|
|
||||||
print ('Successfully updated wallet state on server')
|
print ('Successfully updated wallet state on server')
|
||||||
|
return True
|
||||||
elif response.status_code == 409:
|
elif response.status_code == 409:
|
||||||
conflict = True
|
print ('Submitted wallet is out of date.')
|
||||||
print ('Wallet state out of date. Getting updated wallet state. Try posting again after this.')
|
return False
|
||||||
# Not an error! We still want to merge in the returned wallet.
|
|
||||||
else:
|
else:
|
||||||
print ('Error', response.status_code)
|
print ('Error', response.status_code)
|
||||||
print (response.content)
|
print (response.content)
|
||||||
raise Exception("Unexpected status code")
|
raise Exception("Unexpected status code")
|
||||||
|
|
||||||
wallet_state = WalletState(
|
|
||||||
encrypted_wallet=response.json()['encryptedWallet'],
|
|
||||||
sequence=response.json()['sequence'],
|
|
||||||
)
|
|
||||||
hmac = response.json()['hmac']
|
|
||||||
return wallet_state, hmac, conflict
|
|
||||||
|
|
||||||
def derive_secrets(root_password, salt):
|
def derive_secrets(root_password, salt):
|
||||||
# TODO - Audit me audit me audit me! I don't know if these values are
|
# TODO - Audit me audit me audit me! I don't know if these values are
|
||||||
# optimal.
|
# optimal.
|
||||||
|
@ -256,31 +248,23 @@ class Client():
|
||||||
# now, the SDK handles merges with timestamps and such so it's as safe as
|
# now, the SDK handles merges with timestamps and such so it's as safe as
|
||||||
# always to just merge in.
|
# always to just merge in.
|
||||||
|
|
||||||
# TODO - Be careful of cases where get_remote_wallet comes back with "Not
|
# TODO - Save wallet state to disk, and init by pulling from disk. That way,
|
||||||
# Found" even though a wallet is actually on the server. We would start with
|
# we'll know what the merge base is, and we won't have to merge from 0 each
|
||||||
# sequence=0 with the local encrypted wallet. Then next time we
|
# time the app restarts.
|
||||||
# get_remote_wallet, it returns the actual wallet, let's say sequence=5, and
|
|
||||||
# it would write over what's locally saved. With the SDK as it is using
|
|
||||||
# sync_apply, that's okay because it has a decent way of merging it. However
|
|
||||||
# when the Desktop is in charge of merging, this could be a hazard. Saving
|
|
||||||
# the state to disk could help. Or perhaps pushing first before pulling. Or
|
|
||||||
# maybe if we're writing over sequence=0, we can know we should be "merging"
|
|
||||||
# the local encrypted wallet with whatever comes from the server.
|
|
||||||
|
|
||||||
# TODO - what if two clients initialize, make changes, and then both push?
|
# TODO - Wrap this back into __init__, now that I got the empty encrypted
|
||||||
# We'll need a way to "merge" from a merge base of an empty wallet.
|
# wallet right.
|
||||||
def init_wallet_state(self):
|
def init_wallet_state(self):
|
||||||
if self.get_remote_wallet() == "Not Found":
|
# Represents what's been synced to the wallet sync server. It starts with
|
||||||
print("No wallet found on the server for this account. Starting a new one.")
|
# sequence=0 which means nothing has been synced yet. As such, we start
|
||||||
|
# with an empty encrypted_wallet here. Anything currently in the SDK is a
|
||||||
# Represents what's been synced to the wallet sync server. It starts with
|
# local-only change until it's pushed. If there's a merge conflict,
|
||||||
# sequence=0 which means nothing has been synced yet. We start with
|
# sequence=0, empty encrypted_wallet will be the merge base. That way we
|
||||||
# whatever is in the SDK. Whether it's new or not, we (who choose to run
|
# won't lose any changes.
|
||||||
# this method) are assuming it's not synced yet.
|
self.synced_wallet_state = WalletState(
|
||||||
self.synced_wallet_state = WalletState(
|
sequence=0,
|
||||||
sequence=0,
|
encrypted_wallet="",
|
||||||
encrypted_wallet=self.get_local_encrypted_wallet()
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
success = self.wallet_sync_api.register(
|
success = self.wallet_sync_api.register(
|
||||||
|
@ -308,6 +292,29 @@ class Client():
|
||||||
# we're talking about. Again, we should see how LBRY Desktop/SDK deal with
|
# we're talking about. Again, we should see how LBRY Desktop/SDK deal with
|
||||||
# it.
|
# it.
|
||||||
|
|
||||||
|
def get_merged_wallet_state(self, new_wallet_state):
|
||||||
|
# Eventually, we will look for local changes in
|
||||||
|
# `get_local_encrypted_wallet()` by comparing it to
|
||||||
|
# `self.synced_wallet_state.encrypted_wallet`.
|
||||||
|
#
|
||||||
|
# If there are no local changes, we can just return `new_wallet_state`.
|
||||||
|
#
|
||||||
|
# If there are local changes, we will merge between `new_wallet_state` and
|
||||||
|
# `get_local_encrypted_wallet()`, using
|
||||||
|
# `self.synced_wallet_state.encrypted_wallet` as our merge base.
|
||||||
|
#
|
||||||
|
# For really hairy cases, this could even be a whole interactive process,
|
||||||
|
# not just a function.
|
||||||
|
|
||||||
|
# For now, the SDK handles merging (in a way that we hope to improve with
|
||||||
|
# the above eventually) so we will just return `new_wallet_state`.
|
||||||
|
#
|
||||||
|
# It would be nice to have a little "we just merged in changes" log output
|
||||||
|
# if there are local changes, just for demo purpoes. Unfortunately, the SDK
|
||||||
|
# outputs a different encrypted blob each time we ask it for the encrypted
|
||||||
|
# wallet, so there's no easy way to check if it actually changed.
|
||||||
|
return new_wallet_state
|
||||||
|
|
||||||
# Returns: status
|
# Returns: status
|
||||||
def get_remote_wallet(self):
|
def get_remote_wallet(self):
|
||||||
new_wallet_state, hmac = self.wallet_sync_api.get_wallet(self.auth_token)
|
new_wallet_state, hmac = self.wallet_sync_api.get_wallet(self.auth_token)
|
||||||
|
@ -327,11 +334,14 @@ class Client():
|
||||||
print ('got:', new_wallet_state)
|
print ('got:', new_wallet_state)
|
||||||
return "Error"
|
return "Error"
|
||||||
|
|
||||||
self.synced_wallet_state = new_wallet_state
|
merged_wallet_state = self.get_merged_wallet_state(new_wallet_state)
|
||||||
# TODO errors? sequence of events? This isn't gonna be quite right. Look at state diagrams.
|
|
||||||
self.update_local_encrypted_wallet(new_wallet_state.encrypted_wallet)
|
|
||||||
|
|
||||||
print ("Got latest walletState:")
|
# TODO error recovery between these two steps? sequence of events?
|
||||||
|
# This isn't gonna be quite right. Look at state diagrams.
|
||||||
|
self.synced_wallet_state = merged_wallet_state
|
||||||
|
self.update_local_encrypted_wallet(merged_wallet_state.encrypted_wallet)
|
||||||
|
|
||||||
|
print ("Got (and maybe merged in) latest walletState:")
|
||||||
pprint(self.synced_wallet_state)
|
pprint(self.synced_wallet_state)
|
||||||
return "Success"
|
return "Success"
|
||||||
|
|
||||||
|
@ -351,31 +361,19 @@ class Client():
|
||||||
)
|
)
|
||||||
hmac = create_hmac(submitted_wallet_state, self.hmac_key)
|
hmac = create_hmac(submitted_wallet_state, self.hmac_key)
|
||||||
|
|
||||||
# Submit our wallet, get the latest wallet back as a response
|
# Submit our wallet.
|
||||||
new_wallet_state, new_hmac, conflict = self.wallet_sync_api.update_wallet(submitted_wallet_state, hmac, self.auth_token)
|
updated = self.wallet_sync_api.update_wallet(submitted_wallet_state, hmac, self.auth_token)
|
||||||
|
|
||||||
# TODO - there's some code in common here with the get_remote_wallet function. factor it out.
|
if updated:
|
||||||
|
# We updated it. Now it's synced and we mark it as such.
|
||||||
|
self.synced_wallet_state = submitted_wallet_state
|
||||||
|
|
||||||
if not check_hmac(new_wallet_state, self.hmac_key, new_hmac):
|
print ("Synced walletState:")
|
||||||
print ('Error - bad hmac on new wallet')
|
pprint(self.synced_wallet_state)
|
||||||
print (new_wallet_state, hmac)
|
return "Success"
|
||||||
return "Error"
|
|
||||||
|
|
||||||
if submitted_wallet_state != new_wallet_state and not self._validate_new_wallet_state(new_wallet_state):
|
print ("Could not update. Need to get new wallet and merge")
|
||||||
print ('Error - new wallet does not validate')
|
return "Failure"
|
||||||
print ('current:', self.synced_wallet_state)
|
|
||||||
print ('got:', new_wallet_state)
|
|
||||||
return "Error"
|
|
||||||
|
|
||||||
# TODO - `concflict` determines whether we need to a smart merge here.
|
|
||||||
# However for now the SDK handles all of that.
|
|
||||||
self.synced_wallet_state = new_wallet_state
|
|
||||||
# TODO errors? sequence of events? This isn't gonna be quite right. Look at state diagrams.
|
|
||||||
self.update_local_encrypted_wallet(new_wallet_state.encrypted_wallet)
|
|
||||||
|
|
||||||
print ("Got new walletState:")
|
|
||||||
pprint(self.synced_wallet_state)
|
|
||||||
return "Success"
|
|
||||||
|
|
||||||
def set_preference(self, key, value):
|
def set_preference(self, key, value):
|
||||||
# TODO - error checking
|
# TODO - error checking
|
||||||
|
|
Loading…
Reference in a new issue