Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions fsm/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ func (s *StateMachine) GetAccounts() (result []*Account, err lib.ErrorI) {
if err != nil {
return nil, err
}
// accounts are keyed by address, so the key is authoritative - legacy
// records whose value omits the address would otherwise report empty
if len(acc.Address) == 0 {
addr, e := AddressFromKey(it.Key())
if e != nil {
return nil, e
}
acc.Address = addr.Bytes()
}
result = append(result, acc)
}
// return the result
Expand All @@ -68,11 +77,21 @@ func (s *StateMachine) GetAccountsPaginated(p lib.PageParams) (page *lib.Page, e
// create a new 'accounts' page
page, res := lib.NewPage(p, AccountsPageName), make(AccountPage, 0)
// load the page using the account prefix iterator
err = page.Load(AccountPrefix(), false, &res, s.store, func(_, b []byte) (err lib.ErrorI) {
err = page.Load(AccountPrefix(), false, &res, s.store, func(k, b []byte) (err lib.ErrorI) {
acc, err := s.unmarshalAccount(b)
if err == nil {
res = append(res, acc)
if err != nil {
return
}
// accounts are keyed by address, so the key is authoritative - legacy
// records whose value omits the address would otherwise report empty
if len(acc.Address) == 0 {
addr, e := AddressFromKey(k)
if e != nil {
return e
}
acc.Address = addr.Bytes()
}
res = append(res, acc)
return
})
return
Expand Down
57 changes: 57 additions & 0 deletions fsm/account_addressfromkey_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package fsm

import (
"testing"

"github.com/canopy-network/canopy/lib"
"github.com/canopy-network/canopy/lib/crypto"
"github.com/stretchr/testify/require"
)

// TestGetAccountsRecoversAddressFromKey ensures the bulk account getters report
// an address even when the stored value does not carry one.
//
// Accounts are keyed by address, so the key is authoritative. Records written
// without the address in the value still exist in live state (observed on the
// canoLiq devnet: untouched genesis accounts returned "address": "" from
// /v1/query/accounts, while the same accounts resolved correctly through the
// single-account query, which derives the address from the request). Those
// bytes are never rewritten unless the account transacts, so the read path has
// to tolerate them.
func TestGetAccountsRecoversAddressFromKey(t *testing.T) {
addr := newTestAddress(t)

// write an account record whose marshalled value omits the address,
// reproducing the legacy on-disk shape
writeAddresslessAccount := func(t *testing.T, sm StateMachine, a crypto.AddressI, amount uint64) {
t.Helper()
bz, err := sm.marshalAccount(&Account{Amount: amount}) // no Address
require.NoError(t, err)
require.NoError(t, sm.Set(KeyForAccount(a), bz))
}

t.Run("GetAccounts", func(t *testing.T) {
sm := newTestStateMachine(t)
writeAddresslessAccount(t, sm, addr, 100000000)

got, err := sm.GetAccounts()
require.NoError(t, err)
require.Len(t, got, 1)
require.Equal(t, addr.Bytes(), got[0].Address,
"address must be recovered from the state key")
require.EqualValues(t, 100000000, got[0].Amount)
})

t.Run("GetAccountsPaginated", func(t *testing.T) {
sm := newTestStateMachine(t)
writeAddresslessAccount(t, sm, addr, 100000000)

page, err := sm.GetAccountsPaginated(lib.PageParams{PageNumber: 1, PerPage: 10})
require.NoError(t, err)
results, ok := page.Results.(*AccountPage)
require.True(t, ok)
require.Len(t, *results, 1)
require.Equal(t, addr.Bytes(), (*results)[0].Address,
"address must be recovered from the state key")
})
}