Skip to content
Closed
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
129 changes: 129 additions & 0 deletions ledger/ledgercore/statedelta.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package ledgercore

import (
"encoding/json"
"fmt"

"github.com/algorand/go-algorand/data/basics"
Expand Down Expand Up @@ -126,6 +127,46 @@ type StateDelta struct {
Totals AccountTotals
}

// StateDeltaSerializable is nearly identical to StateDelta,
// but is able to be serialized/deserialized to/from JSON without custom
// MarshalJSON() and UnmarshalJSON() methods.
type StateDeltaSerializable struct {
// modified new accounts
Accts AccountDeltas

// modified kv pairs (nil == delete)
// not preallocated use .AddKvMod to insert instead of direct assignment
KvMods map[string]KvValueDelta

// new Txids for the txtail and TxnCounter, mapped to txn.LastValid
Txids map[string]IncludedTransactions

// new txleases for the txtail mapped to expiration
// not pre-allocated so use .AddTxLease to insert instead of direct assignment
Txleases map[string]basics.Round

// new creatables creator lookup table
// not pre-allocated so use .AddCreatable to insert instead of direct assignment
Creatables map[basics.CreatableIndex]ModifiedCreatable

// new block header; read-only
Hdr bookkeeping.BlockHeader

// StateProofNext represents modification on StateProofNextRound field in the block header. If the block contains
// a valid state proof transaction, this field will contain the next round for state proof.
// otherwise it will be set to 0.
StateProofNext basics.Round

// previous block timestamp
PrevTimestamp int64

// initial hint for allocating data structures for StateDelta
initialHint int

// The account totals reflecting the changes in this StateDelta object.
Totals AccountTotals
}

// BalanceRecord is similar to basics.BalanceRecord but with decoupled base and voting data
type BalanceRecord struct {
Addr basics.Address
Expand Down Expand Up @@ -246,6 +287,94 @@ func (sd *StateDelta) Dehydrate() {
}
}

// ToSerializable() converts a StateDeltaSerializable to a StateDelta
func (sd StateDelta) ToSerializable() (StateDeltaSerializable, error) {
serializableTxleases := map[string]basics.Round{}
for k, v := range sd.Txleases {
json, err := json.Marshal(k)
if err != nil {
return StateDeltaSerializable{}, err
}
serializableTxleases[string(json)] = v
}
serializableTxids := map[string]IncludedTransactions{}
for k, v := range sd.Txids {
json := k.String()
serializableTxids[string(json)] = v
}
return StateDeltaSerializable{
Accts: sd.Accts,
KvMods: sd.KvMods,
Txids: serializableTxids,
Txleases: serializableTxleases,
Creatables: sd.Creatables,
Hdr: *sd.Hdr,
StateProofNext: sd.StateProofNext,
PrevTimestamp: sd.PrevTimestamp,
initialHint: sd.initialHint,
Totals: sd.Totals,
}, nil
}

// MarshalJSON() encodes a StateDelta into JSON
func (sd StateDelta) MarshalJSON() ([]byte, error) {
serializable, err := sd.ToSerializable()
if err != nil {
return nil, err
}
serialized, err := json.Marshal(serializable)
return serialized, err
}

// UnmarshalJSON() converts JSON into a StateDelta
func (sd *StateDelta) UnmarshalJSON(data []byte) error {
var serializable StateDeltaSerializable
err := json.Unmarshal(data, &serializable)
if err != nil {
return err
}
nonSerializable, err := serializable.ToNonSerializable()
if err != nil {
return err
}
*sd = nonSerializable
return nil
}

// ToNonSerializable() converts a StateDeltaSerializable to a StateDelta
func (sd StateDeltaSerializable) ToNonSerializable() (StateDelta, error) {
nonSerializableTxleases := map[Txlease]basics.Round{}
for k, v := range sd.Txleases {
var txlease Txlease
err := json.Unmarshal([]byte(k), &txlease)
if err != nil {
return StateDelta{}, err
}
nonSerializableTxleases[txlease] = v
}
nonSerializableTxids := map[transactions.Txid]IncludedTransactions{}
for k, v := range sd.Txids {
var txid transactions.Txid
err := txid.UnmarshalText([]byte(k))
if err != nil {
return StateDelta{}, err
}
nonSerializableTxids[txid] = v
}
return StateDelta{
Accts: sd.Accts,
KvMods: sd.KvMods,
Txids: nonSerializableTxids,
Txleases: nonSerializableTxleases,
Creatables: sd.Creatables,
Hdr: &sd.Hdr,
StateProofNext: sd.StateProofNext,
PrevTimestamp: sd.PrevTimestamp,
initialHint: sd.initialHint,
Totals: sd.Totals,
}, nil
}

// MakeAccountDeltas creates account delta
// if adding new fields make sure to add them to the .reset() and .isEmpty() methods
func MakeAccountDeltas(hint int) AccountDeltas {
Expand Down
46 changes: 46 additions & 0 deletions ledger/ledgercore/statedelta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package ledgercore

import (
"encoding/json"
"reflect"
"testing"
"fmt"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -539,6 +541,50 @@ func TestStateDeltaReflect(t *testing.T) {
}
}

func TestStateDeltaJSON(t *testing.T) {
partitiontest.PartitionTest(t)
sd := StateDelta{
Accts: AccountDeltas{
Accts: []BalanceRecord{},
AppResources: []AppResourceRecord{},
AssetResources: []AssetResourceRecord{},
},
KvMods: map[string]KvValueDelta{
"123": KvValueDelta{
Data: []byte("abc"),
OldData: []byte("xyz"),
},
},
Txids: map[transactions.Txid]IncludedTransactions{
transactions.Txid{}: IncludedTransactions {
},
},
Txleases: map[Txlease]basics.Round{
Txlease{
Sender: basics.Address{},
Lease: [32]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
}: basics.Round(123),
},
Creatables: map[basics.CreatableIndex]ModifiedCreatable{},
Hdr: &bookkeeping.BlockHeader{},
StateProofNext: basics.Round(123),
PrevTimestamp: 123,
initialHint: 0, // Ignore initialHint as it's not exported
Totals: AccountTotals{},
}
encoded, err := json.Marshal(sd)
if err != nil {
panic(err)
}
fmt.Println(string(encoded))
var decoded StateDelta
err = json.Unmarshal(encoded, &decoded)
if err != nil {
panic(err)
}
assert.Equal(t, sd, decoded)
}

func TestAccountDeltaReflect(t *testing.T) {
partitiontest.PartitionTest(t)

Expand Down