-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from philippgille/feature/decode-account-state
Feature/decode account state
- Loading branch information
Showing
7 changed files
with
284 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,3 +13,5 @@ before_install: | |
script: | ||
# Build | ||
- go build -v ./... | ||
# Run tests | ||
- go test -v -race . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package libra | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
) | ||
|
||
const ( | ||
// See https://github.com/perfectmak/libra-core/blob/6dc45f7b72aee0162d52a9d53d99cdae1adae6f1/lib/constants/PathValues.ts | ||
accResourceKey = "01217da6c6b3e19f1825cfb2676daecce3bf3de03cf26647c78df00b371b25cc97" | ||
) | ||
|
||
// AccountState represents the state of an account. | ||
type AccountState struct { | ||
// The whole account state as raw bytes | ||
Blob []byte | ||
// The account resource with balance etc. | ||
AccountResource AccountResource | ||
// TODO: AccountEvents? | ||
} | ||
|
||
// FromAccountStateBlob converts an account state blob into an object of the AccountState struct. | ||
func FromAccountStateBlob(accountStateBlob []byte) (AccountState, error) { | ||
result := AccountState{ | ||
Blob: accountStateBlob, | ||
} | ||
|
||
r := bytes.NewReader(accountStateBlob) | ||
|
||
// Inspired by https://github.com/perfectmak/libra-core/blob/6dc45f7b72aee0162d52a9d53d99cdae1adae6f1/lib/client.ts#L250 | ||
|
||
var mapEntryCount uint32 | ||
err := binary.Read(r, binary.LittleEndian, &mapEntryCount) | ||
if err != nil { | ||
return result, err | ||
} | ||
|
||
m := make(map[string][]byte, mapEntryCount) | ||
|
||
// For now we only decode the account state. | ||
// Go through the whole blob to find it (it has a specific map key). | ||
// TODO: Also take care of events. | ||
accResourceKeyFound := false | ||
for mapEntryNo := uint32(1); mapEntryNo <= mapEntryCount; mapEntryNo++ { | ||
var mapKeyLen uint32 | ||
err := binary.Read(r, binary.LittleEndian, &mapKeyLen) | ||
if err != nil { | ||
return result, err | ||
} | ||
mapKey := make([]byte, mapKeyLen) | ||
err = binary.Read(r, binary.LittleEndian, &mapKey) | ||
if err != nil { | ||
return result, err | ||
} | ||
|
||
var mapValLength uint32 | ||
err = binary.Read(r, binary.LittleEndian, &mapValLength) | ||
if err != nil { | ||
return result, err | ||
} | ||
mapVal := make([]byte, mapValLength) | ||
err = binary.Read(r, binary.LittleEndian, &mapVal) | ||
if err != nil { | ||
return result, err | ||
} | ||
|
||
mapKeyAsString := hex.EncodeToString(mapKey) | ||
m[mapKeyAsString] = mapVal | ||
|
||
// TODO: Remove when adding handling of events | ||
if mapKeyAsString == accResourceKey { | ||
accResourceKeyFound = true | ||
break | ||
} | ||
} | ||
|
||
if !accResourceKeyFound { | ||
return result, errors.New("The account state blob didn't contain the data of an account resource or there was an error decoding it") | ||
} | ||
|
||
accResource, err := FromAccountResourceBlob(m[accResourceKey]) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.AccountResource = accResource | ||
|
||
return result, nil | ||
} | ||
|
||
// AccountResource represents an account with its balance etc. | ||
type AccountResource struct { | ||
AuthKey []byte | ||
Balance uint64 | ||
ReceivedEvents uint64 | ||
SentEvents uint64 | ||
SequenceNo uint64 | ||
} | ||
|
||
// String formats the account state similarly to the Libra CLI. | ||
// Numbers are formatted as string because the numbers are uint64, | ||
// whose max value exceeds JSON's "save integer", | ||
// which can lead to parsing errors. | ||
func (ar AccountResource) String() string { | ||
return fmt.Sprintf("{\"authentication_key\": \"0x%x\", \"balance\": \"%d\", \"received_events_count\": \"%d\", \"sent_events_count\": \"%d\", \"sequence_number\": \"%d\"}", | ||
ar.AuthKey, ar.Balance, ar.ReceivedEvents, ar.SentEvents, ar.SequenceNo) | ||
} | ||
|
||
// FromAccountResourceBlob converts an account resource blob into an object of the AccountState struct. | ||
func FromAccountResourceBlob(accountResourceBlob []byte) (AccountResource, error) { | ||
result := AccountResource{} | ||
|
||
r := bytes.NewReader(accountResourceBlob) | ||
|
||
var authKeyLen uint32 | ||
err := binary.Read(r, binary.LittleEndian, &authKeyLen) | ||
if err != nil { | ||
return result, err | ||
} | ||
|
||
authKey := make([]byte, authKeyLen) | ||
err = binary.Read(r, binary.LittleEndian, &authKey) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.AuthKey = authKey | ||
|
||
var balance uint64 | ||
err = binary.Read(r, binary.LittleEndian, &balance) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.Balance = balance | ||
|
||
var receivedEvents uint64 | ||
err = binary.Read(r, binary.LittleEndian, &receivedEvents) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.ReceivedEvents = receivedEvents | ||
|
||
var sentEvents uint64 | ||
err = binary.Read(r, binary.LittleEndian, &sentEvents) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.SentEvents = sentEvents | ||
|
||
var sequenceNo uint64 | ||
err = binary.Read(r, binary.LittleEndian, &sequenceNo) | ||
if err != nil { | ||
return result, err | ||
} | ||
result.SequenceNo = sequenceNo | ||
|
||
return result, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package libra_test | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/go-test/deep" | ||
|
||
libra "github.com/philippgille/libra-sdk-go" | ||
) | ||
|
||
const ( | ||
// In the Libra CLI: | ||
// | ||
//Decoded: AccountResource { | ||
// authentication_key: 0x8cd377191fe0ef113455c8e8d769f0c0147d5bb618bf195c0af31a05fbfd0969, | ||
// balance: 62500000, | ||
// received_events_count: 1, | ||
// sent_events_count: 4, | ||
// sequence_number: 4, | ||
// } | ||
testAcc1StateString = "010000002100000001217da6c6b3e19f1825cfb2676daecce3bf3de03cf26647c78df00b371b25cc9744000000200000008cd377191fe0ef113455c8e8d769f0c0147d5bb618bf195c0af31a05fbfd0969a0acb90300000000010000000000000004000000000000000400000000000000" | ||
// The account resource part of the above account state blob | ||
testAcc1ResString = "200000008cd377191fe0ef113455c8e8d769f0c0147d5bb618bf195c0af31a05fbfd0969a0acb90300000000010000000000000004000000000000000400000000000000" | ||
// The auth key part of the above account resource blob | ||
testAcc1AuthKey = "8cd377191fe0ef113455c8e8d769f0c0147d5bb618bf195c0af31a05fbfd0969" | ||
) | ||
|
||
// TestAccountStateDecoding tests if libra.FromAccountStateBlob(...) works correctly. | ||
// It uses libra.FromAccountResourceBlob(...), so that function must be tested independently. | ||
func TestAccountStateDecoding(t *testing.T) { | ||
testAccState, err := hex.DecodeString(testAcc1StateString) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
accState, err := libra.FromAccountStateBlob(testAccState) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !bytes.Equal(accState.Blob, testAccState) { | ||
t.Fatal("accState.Blob != expected account state blob") | ||
} | ||
accRes := accState.AccountResource | ||
if diff := deep.Equal(accRes, libra.AccountResource{}); diff == nil { | ||
t.Fatal("accState.AccountResource has a nil value, but should be an object with values") | ||
} | ||
|
||
// Construct expected account resource | ||
expectedAccResBlob, err := hex.DecodeString(testAcc1ResString) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
expectedAccRes, err := libra.FromAccountResourceBlob(expectedAccResBlob) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if diff := deep.Equal(accRes, expectedAccRes); diff != nil { | ||
t.Fatal(diff) | ||
} | ||
} | ||
|
||
// TestAccountResourceDecoding tests if libra.FromAccountResourceBlob(...) works correctly. | ||
func TestAccountResourceDecoding(t *testing.T) { | ||
// The string is a substring of the account state tested elsewhere | ||
testAccRes, err := hex.DecodeString(testAcc1ResString) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
accRes, err := libra.FromAccountResourceBlob(testAccRes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if diff := deep.Equal(accRes, libra.AccountResource{}); diff == nil { | ||
t.Fatal("accRes is a nil value, but should be an object with values") | ||
} | ||
expectedAuthKey, err := hex.DecodeString(testAcc1AuthKey) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !bytes.Equal(accRes.AuthKey, expectedAuthKey) { | ||
t.Fatal("accRes.AuthKey != expected auth key") | ||
} | ||
if accRes.Balance != uint64(62500000) { | ||
t.Fatal("accRes.Balance != 62500000") | ||
} | ||
if accRes.ReceivedEvents != uint64(1) { | ||
t.Fatal("accRes.ReceivedEvents != 1") | ||
} | ||
if accRes.SentEvents != uint64(4) { | ||
t.Fatal("accRes.SentEvents != 4") | ||
} | ||
if accRes.SequenceNo != uint64(4) { | ||
t.Fatal("accRes.SequenceNo != 4") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters