diff --git a/pkg/proto/block_snapshot.go b/pkg/proto/block_snapshot.go index 0f6edcf72..fff495935 100644 --- a/pkg/proto/block_snapshot.go +++ b/pkg/proto/block_snapshot.go @@ -3,18 +3,34 @@ package proto import ( "encoding/binary" "encoding/json" - "github.com/pkg/errors" g "github.com/wavesplatform/gowaves/pkg/grpc/generated/waves" ) type BlockSnapshot struct { - TxSnapshots [][]AtomicSnapshot + TransactionsSnapshots []TxSnapshot } func (bs *BlockSnapshot) AppendTxSnapshot(txSnapshot []AtomicSnapshot) { - bs.TxSnapshots = append(bs.TxSnapshots, txSnapshot) + bs.TransactionsSnapshots = append(bs.TransactionsSnapshots, txSnapshot) +} + +// Equal function assumes that TransactionsSnapshots are in same order in both original and other instances. +func (bs BlockSnapshot) Equal(other BlockSnapshot) (bool, error) { + if len(bs.TransactionsSnapshots) != len(other.TransactionsSnapshots) { + return false, nil + } + for i, txSnapshot := range bs.TransactionsSnapshots { + equal, err := txSnapshot.Equal(other.TransactionsSnapshots[i]) + if err != nil { + return false, err + } + if !equal { + return false, nil + } + } + return true, nil } func (bs *BlockSnapshot) AppendTxSnapshots(txSnapshots [][]AtomicSnapshot) { @@ -22,8 +38,8 @@ func (bs *BlockSnapshot) AppendTxSnapshots(txSnapshots [][]AtomicSnapshot) { } func (bs BlockSnapshot) MarshallBinary() ([]byte, error) { - result := binary.BigEndian.AppendUint32([]byte{}, uint32(len(bs.TxSnapshots))) - for _, ts := range bs.TxSnapshots { + result := binary.BigEndian.AppendUint32([]byte{}, uint32(len(bs.TransactionsSnapshots))) + for _, ts := range bs.TransactionsSnapshots { var res g.TransactionStateSnapshot for _, atomicSnapshot := range ts { if err := atomicSnapshot.AppendToProtobuf(&res); err != nil { @@ -46,7 +62,7 @@ func (bs *BlockSnapshot) UnmarshalBinary(data []byte, scheme Scheme) error { } txSnCnt := binary.BigEndian.Uint32(data[0:uint32Size]) data = data[uint32Size:] - var txSnapshots [][]AtomicSnapshot + var txSnapshots []TxSnapshot for i := uint32(0); i < txSnCnt; i++ { if len(data) < uint32Size { return errors.Errorf("BlockSnapshot UnmarshallBinary: invalid data size") @@ -68,7 +84,7 @@ func (bs *BlockSnapshot) UnmarshalBinary(data []byte, scheme Scheme) error { txSnapshots = append(txSnapshots, atomicTS) data = data[tsBytesLen:] } - bs.TxSnapshots = txSnapshots + bs.TransactionsSnapshots = txSnapshots return nil } @@ -88,11 +104,11 @@ func (bs BlockSnapshot) ToProtobuf() ([]*g.TransactionStateSnapshot, error) { } func (bs BlockSnapshot) MarshalJSON() ([]byte, error) { - if len(bs.TxSnapshots) == 0 { + if len(bs.TransactionsSnapshots) == 0 { return []byte("[]"), nil } - res := make([]txSnapshotJSON, 0, len(bs.TxSnapshots)) - for _, txSnapshot := range bs.TxSnapshots { + res := make([]txSnapshotJSON, 0, len(bs.TransactionsSnapshots)) + for _, txSnapshot := range bs.TransactionsSnapshots { var js txSnapshotJSON for _, snapshot := range txSnapshot { if err := snapshot.Apply(&js); err != nil { @@ -110,10 +126,10 @@ func (bs *BlockSnapshot) UnmarshalJSON(bytes []byte) error { return err } if len(blockSnapshotJSON) == 0 { - bs.TxSnapshots = nil + bs.TransactionsSnapshots = nil return nil } - res := make([][]AtomicSnapshot, 0, len(blockSnapshotJSON)) + res := make([]TxSnapshot, 0, len(blockSnapshotJSON)) for _, js := range blockSnapshotJSON { txSnapshot, err := js.toTransactionSnapshot() if err != nil { @@ -121,7 +137,7 @@ func (bs *BlockSnapshot) UnmarshalJSON(bytes []byte) error { } res = append(res, txSnapshot) } - bs.TxSnapshots = res + bs.TransactionsSnapshots = res return nil } diff --git a/pkg/proto/block_snapshot_test.go b/pkg/proto/block_snapshot_test.go index ebb45f7e8..85305b806 100644 --- a/pkg/proto/block_snapshot_test.go +++ b/pkg/proto/block_snapshot_test.go @@ -325,7 +325,7 @@ func Test_txSnapshotJSON_MarshalJSON_UnmarshalJSON(t *testing.T) { } // Test marshalling and unmarshalling txSnapshotJSON. - bs := proto.BlockSnapshot{TxSnapshots: [][]proto.AtomicSnapshot{ + bs := proto.BlockSnapshot{TransactionsSnapshots: []proto.TxSnapshot{ succeededTxSnap, failedTxSnap, elidedTxSnap, @@ -337,18 +337,18 @@ func Test_txSnapshotJSON_MarshalJSON_UnmarshalJSON(t *testing.T) { var unmBs proto.BlockSnapshot err = json.Unmarshal(data, &unmBs) require.NoError(t, err) - assert.Len(t, unmBs.TxSnapshots, len(bs.TxSnapshots)) - for i := range bs.TxSnapshots { - assert.ElementsMatch(t, bs.TxSnapshots[i], unmBs.TxSnapshots[i]) + assert.Len(t, unmBs.TransactionsSnapshots, len(bs.TransactionsSnapshots)) + for i := range bs.TransactionsSnapshots { + assert.ElementsMatch(t, bs.TransactionsSnapshots[i], unmBs.TransactionsSnapshots[i]) } // Test empty BlockSnapshot. - data, err = json.Marshal(proto.BlockSnapshot{TxSnapshots: [][]proto.AtomicSnapshot{}}) + data, err = json.Marshal(proto.BlockSnapshot{TransactionsSnapshots: []proto.TxSnapshot{}}) require.NoError(t, err) assert.Equal(t, "[]", string(data)) // Test BlockSnapshot with nil txSnapshots. - data, err = json.Marshal(proto.BlockSnapshot{TxSnapshots: nil}) + data, err = json.Marshal(proto.BlockSnapshot{TransactionsSnapshots: nil}) require.NoError(t, err) assert.Equal(t, "[]", string(data)) @@ -356,6 +356,173 @@ func Test_txSnapshotJSON_MarshalJSON_UnmarshalJSON(t *testing.T) { var unmEmptyBs proto.BlockSnapshot err = json.Unmarshal(data, &unmEmptyBs) require.NoError(t, err) - assert.Len(t, unmEmptyBs.TxSnapshots, 0) - assert.Nil(t, unmEmptyBs.TxSnapshots) + assert.Len(t, unmEmptyBs.TransactionsSnapshots, 0) + assert.Nil(t, unmEmptyBs.TransactionsSnapshots) +} + +// TestBlockSnapshotEqual tests the comparison of two BlockSnapshot instances. +func TestBlockSnapshotEqual(t *testing.T) { + addr1, _ := proto.NewAddressFromString("3P9o3uwx3fWZz3b53g53ARUk9sFoPW6z7HA") + addr2, _ := proto.NewAddressFromString("3P9o3uwx3fWZz3b5aaaaaaaaaaFoPW6z7HB") + assetID1 := crypto.MustDigestFromBase58("BrjV5AB5S7qN5tLQFbU5tpLj5qeozfVvPxEpDkmmhNP") + assetID2 := crypto.MustDigestFromBase58("5Zv8JLH8TTvq9iCo6HtB2K7CGpTJt6JTj5yvXaDVrxEJ") + publicKey1, _ := crypto.NewPublicKeyFromBase58("5TBjL2VdL1XmXq5dC4SYMeH5sVCGmMTeBNNYqWCuEXMn") + leaseID1 := crypto.MustDigestFromBase58("FjnZ7aY8iqVpZc4M4uPFuDzMB6YShYd4cNmRfQP1p4Su") + + // Setup test cases + tests := []struct { + name string + blockSnapshotA proto.BlockSnapshot + blockSnapshotB proto.BlockSnapshot + wantEqual bool + }{ + { + name: "equal snapshots with single transaction", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr1, Balance: 100}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr1, Balance: 100}}, + }, + }, + wantEqual: true, + }, + { + name: "different snapshots with single transaction", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr1, Balance: 100}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr2, Balance: 100}}, + }, + }, + wantEqual: false, + }, + { + name: "equal snapshots with multiple transactions", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr1, Balance: 100}}, + {&proto.AssetBalanceSnapshot{Address: addr2, AssetID: assetID1, Balance: 200}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.WavesBalanceSnapshot{Address: addr1, Balance: 100}}, + {&proto.AssetBalanceSnapshot{Address: addr2, AssetID: assetID1, Balance: 200}}, + }, + }, + wantEqual: true, + }, + { + name: "snapshots with different asset balances", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetBalanceSnapshot{Address: addr1, AssetID: assetID1, Balance: 300}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetBalanceSnapshot{Address: addr1, AssetID: assetID2, Balance: 300}}, + }, + }, + wantEqual: false, + }, + { + name: "snapshots with new lease and cancelled lease snapshots", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.NewLeaseSnapshot{LeaseID: leaseID1, Amount: 1000, SenderPK: publicKey1, RecipientAddr: addr1}}, + {&proto.CancelledLeaseSnapshot{LeaseID: leaseID1}}, + }, + }, + wantEqual: false, + }, + { + name: "snapshots with equal AssetVolumeSnapshot", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetVolumeSnapshot{AssetID: assetID1, TotalQuantity: *big.NewInt(1000), IsReissuable: true}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetVolumeSnapshot{AssetID: assetID1, TotalQuantity: *big.NewInt(1000), IsReissuable: true}}, + }, + }, + wantEqual: true, + }, + { + name: "snapshots with different AssetVolumeSnapshot reissuability", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetVolumeSnapshot{AssetID: assetID1, TotalQuantity: *big.NewInt(1000), IsReissuable: true}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.AssetVolumeSnapshot{AssetID: assetID1, TotalQuantity: *big.NewInt(1000), IsReissuable: false}}, + }, + }, + wantEqual: false, + }, + { + name: "snapshots with equal DataEntriesSnapshot", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.DataEntriesSnapshot{Address: addr1, DataEntries: proto.DataEntries{ + &proto.IntegerDataEntry{Key: "key1", Value: 100}, + &proto.BooleanDataEntry{Key: "key2", Value: true}, + }}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.DataEntriesSnapshot{Address: addr1, DataEntries: proto.DataEntries{ + &proto.IntegerDataEntry{Key: "key1", Value: 100}, + &proto.BooleanDataEntry{Key: "key2", Value: true}, + }}}, + }, + }, + wantEqual: true, + }, + { + name: "snapshots with different DataEntriesSnapshot", + blockSnapshotA: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.DataEntriesSnapshot{Address: addr1, DataEntries: proto.DataEntries{ + &proto.IntegerDataEntry{Key: "key1", Value: 100}, + &proto.BooleanDataEntry{Key: "key2", Value: true}, + }}}, + }, + }, + blockSnapshotB: proto.BlockSnapshot{ + TransactionsSnapshots: []proto.TxSnapshot{ + {&proto.DataEntriesSnapshot{Address: addr1, DataEntries: proto.DataEntries{ + &proto.IntegerDataEntry{Key: "key1", Value: 200}, // Different value + &proto.BooleanDataEntry{Key: "key2", Value: true}, + }}}, + }, + }, + wantEqual: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + equal, err := tt.blockSnapshotA.Equal(tt.blockSnapshotB) + if err != nil { + t.Errorf("Error comparing snapshots: %v", err) + } + if equal != tt.wantEqual { + t.Errorf("Expected snapshots to be equal: %v, got: %v", tt.wantEqual, equal) + } + }) + } } diff --git a/pkg/proto/snapshot_types.go b/pkg/proto/snapshot_types.go index 238872d19..fc0fca083 100644 --- a/pkg/proto/snapshot_types.go +++ b/pkg/proto/snapshot_types.go @@ -1,8 +1,12 @@ package proto import ( + "bytes" "encoding/json" + "fmt" "math/big" + "reflect" + "sort" "github.com/pkg/errors" @@ -13,8 +17,8 @@ import ( type AtomicSnapshot interface { Apply(SnapshotApplier) error - /* TODO remove it. It is temporarily used to mark snapshots generated by tx diff that shouldn't be applied, - because balances diffs are applied later in the block. */ + Equal(otherSnapshot AtomicSnapshot) (bool, error) + String() string AppendToProtobuf(txSnapshots *g.TransactionStateSnapshot) error } @@ -31,6 +35,18 @@ func (s WavesBalanceSnapshot) MarshalJSON() ([]byte, error) { return json.Marshal(out) } +func (s WavesBalanceSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*WavesBalanceSnapshot) + if !ok { + return false, errors.Errorf("expected WavesBalanceSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.Address == other.Address && s.Balance == other.Balance, nil +} + +func (s WavesBalanceSnapshot) String() string { + return fmt.Sprintf("WavesBalanceSnapshot{Address: %s, Balance: %d}", s.Address.String(), s.Balance) +} + func (s WavesBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyWavesBalance(s) } func (s WavesBalanceSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_Balance, error) { @@ -78,6 +94,19 @@ type AssetBalanceSnapshot struct { func (s AssetBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetBalance(s) } +func (s AssetBalanceSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AssetBalanceSnapshot) + if !ok { + return false, errors.Errorf("expected AssetBalanceSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.Address == other.Address && s.AssetID == other.AssetID && s.Balance == other.Balance, nil +} + +func (s AssetBalanceSnapshot) String() string { + return fmt.Sprintf("AssetBalanceSnapshot{Address: %s, AssetID: %s, Balance: %d}", + s.Address.String(), s.AssetID.String(), s.Balance) +} + func (s AssetBalanceSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_Balance, error) { return &g.TransactionStateSnapshot_Balance{ Address: s.Address.Bytes(), @@ -123,6 +152,28 @@ type DataEntriesSnapshot struct { // AccountData in pb func (s DataEntriesSnapshot) Apply(a SnapshotApplier) error { return a.ApplyDataEntries(s) } +func (s DataEntriesSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*DataEntriesSnapshot) + if !ok { + return false, errors.Errorf("expected DataEntriesSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + if s.Address != other.Address || len(s.DataEntries) != len(other.DataEntries) { + return false, nil + } + equalDE, err := s.DataEntries.Equal(other.DataEntries) + if err != nil { + return false, err + } + if !equalDE { + return false, nil + } + return true, nil +} + +func (s DataEntriesSnapshot) String() string { + return fmt.Sprintf("DataEntriesSnapshot{Address: %s, DataEntries: %s}", s.Address.String(), s.DataEntries.String()) +} + func (s DataEntriesSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_AccountData, error) { entries := make([]*g.DataEntry, 0, len(s.DataEntries)) for _, e := range s.DataEntries { @@ -169,6 +220,21 @@ type AccountScriptSnapshot struct { func (s AccountScriptSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAccountScript(s) } +func (s AccountScriptSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AccountScriptSnapshot) + if !ok { + return false, errors.Errorf("expected AccountScriptSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + + return s.SenderPublicKey == other.SenderPublicKey && + bytes.Equal(s.Script, other.Script) && s.VerifierComplexity == other.VerifierComplexity, nil +} + +func (s AccountScriptSnapshot) String() string { + return fmt.Sprintf("AccountScriptSnapshot{SenderPublicKey: %s, Script: %x, VerifierComplexity: %d}", + s.SenderPublicKey.String(), s.Script.String(), s.VerifierComplexity) +} + func (s AccountScriptSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_AccountScript, error) { return &g.TransactionStateSnapshot_AccountScript{ SenderPublicKey: s.SenderPublicKey.Bytes(), @@ -217,6 +283,18 @@ type AssetScriptSnapshot struct { func (s AssetScriptSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetScript(s) } +func (s AssetScriptSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AssetScriptSnapshot) + if !ok { + return false, errors.Errorf("expected AssetScriptSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.AssetID == other.AssetID && bytes.Equal(s.Script, other.Script), nil +} + +func (s AssetScriptSnapshot) String() string { + return fmt.Sprintf("AssetScriptSnapshot{AssetID: %s, Script: %x}", s.AssetID.String(), s.Script.String()) +} + func (s AssetScriptSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_AssetScript, error) { return &g.TransactionStateSnapshot_AssetScript{ AssetId: s.AssetID.Bytes(), @@ -259,6 +337,19 @@ type LeaseBalanceSnapshot struct { func (s LeaseBalanceSnapshot) Apply(a SnapshotApplier) error { return a.ApplyLeaseBalance(s) } +func (s LeaseBalanceSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*LeaseBalanceSnapshot) + if !ok { + return false, errors.Errorf("expected LeaseBalanceSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.Address == other.Address && s.LeaseIn == other.LeaseIn && s.LeaseOut == other.LeaseOut, nil +} + +func (s LeaseBalanceSnapshot) String() string { + return fmt.Sprintf("LeaseBalanceSnapshot{Address: %s, LeaseIn: %d, LeaseOut: %d}", + s.Address.String(), s.LeaseIn, s.LeaseOut) +} + func (s LeaseBalanceSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_LeaseBalance, error) { return &g.TransactionStateSnapshot_LeaseBalance{ Address: s.Address.Bytes(), @@ -300,6 +391,22 @@ type NewLeaseSnapshot struct { func (s NewLeaseSnapshot) Apply(a SnapshotApplier) error { return a.ApplyNewLease(s) } +func (s NewLeaseSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*NewLeaseSnapshot) + if !ok { + return false, errors.Errorf("expected NewLeaseSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.LeaseID == other.LeaseID && + s.Amount == other.Amount && + s.SenderPK == other.SenderPK && + s.RecipientAddr == other.RecipientAddr, nil +} + +func (s NewLeaseSnapshot) String() string { + return fmt.Sprintf("NewLeaseSnapshot{LeaseID: %s, Amount: %d, SenderPK: %s, RecipientAddr: %s}", + s.LeaseID.String(), s.Amount, s.SenderPK.String(), s.RecipientAddr.String()) +} + func (s NewLeaseSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_NewLease, error) { return &g.TransactionStateSnapshot_NewLease{ LeaseId: s.LeaseID.Bytes(), @@ -351,6 +458,18 @@ type CancelledLeaseSnapshot struct { func (s CancelledLeaseSnapshot) Apply(a SnapshotApplier) error { return a.ApplyCancelledLease(s) } +func (s CancelledLeaseSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*CancelledLeaseSnapshot) + if !ok { + return false, errors.Errorf("expected CancelledLeaseSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.LeaseID == other.LeaseID, nil +} + +func (s CancelledLeaseSnapshot) String() string { + return fmt.Sprintf("CancelledLeaseSnapshot{LeaseID: %s}", s.LeaseID.String()) +} + func (s CancelledLeaseSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_CancelledLease, error) { return &g.TransactionStateSnapshot_CancelledLease{ LeaseId: s.LeaseID.Bytes(), @@ -383,6 +502,18 @@ type SponsorshipSnapshot struct { func (s SponsorshipSnapshot) Apply(a SnapshotApplier) error { return a.ApplySponsorship(s) } +func (s SponsorshipSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*SponsorshipSnapshot) + if !ok { + return false, errors.Errorf("expected SponsorshipSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.AssetID == other.AssetID && s.MinSponsoredFee == other.MinSponsoredFee, nil +} + +func (s SponsorshipSnapshot) String() string { + return fmt.Sprintf("SponsorshipSnapshot{AssetID: %s, MinSponsoredFee: %d}", s.AssetID.String(), s.MinSponsoredFee) +} + func (s SponsorshipSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_Sponsorship, error) { return &g.TransactionStateSnapshot_Sponsorship{ AssetId: s.AssetID.Bytes(), @@ -430,6 +561,18 @@ func (s *AliasSnapshot) UnmarshalJSON(bytes []byte) error { func (s AliasSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAlias(s) } +func (s AliasSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AliasSnapshot) + if !ok { + return false, errors.Errorf("expected AliasSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.Address == other.Address && s.Alias == other.Alias, nil +} + +func (s AliasSnapshot) String() string { + return fmt.Sprintf("AliasSnapshot{Address: %s, Alias: %s}", s.Address.String(), s.Alias) +} + func (s AliasSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_Alias, error) { return &g.TransactionStateSnapshot_Alias{ Address: s.Address.Bytes(), @@ -471,6 +614,19 @@ type FilledVolumeFeeSnapshot struct { // OrderFill func (s FilledVolumeFeeSnapshot) Apply(a SnapshotApplier) error { return a.ApplyFilledVolumeAndFee(s) } +func (s FilledVolumeFeeSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*FilledVolumeFeeSnapshot) + if !ok { + return false, errors.Errorf("expected FilledVolumeFeeSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.OrderID == other.OrderID && s.FilledVolume == other.FilledVolume && s.FilledFee == other.FilledFee, nil +} + +func (s FilledVolumeFeeSnapshot) String() string { + return fmt.Sprintf("FilledVolumeFeeSnapshot{OrderID: %s, FilledVolume: %d, FilledFee: %d}", + s.OrderID.String(), s.FilledVolume, s.FilledFee) +} + func (s FilledVolumeFeeSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_OrderFill, error) { return &g.TransactionStateSnapshot_OrderFill{ OrderId: s.OrderID.Bytes(), @@ -517,6 +673,20 @@ type NewAssetSnapshot struct { func (s NewAssetSnapshot) Apply(a SnapshotApplier) error { return a.ApplyNewAsset(s) } +func (s NewAssetSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*NewAssetSnapshot) + if !ok { + return false, errors.Errorf("expected NewAssetSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.AssetID == other.AssetID && s.IssuerPublicKey == other.IssuerPublicKey && + s.Decimals == other.Decimals && s.IsNFT == other.IsNFT, nil +} + +func (s NewAssetSnapshot) String() string { + return fmt.Sprintf("NewAssetSnapshot{AssetID: %s, IssuerPublicKey: %s, Decimals: %d, IsNFT: %t}", + s.AssetID.String(), s.IssuerPublicKey.String(), s.Decimals, s.IsNFT) +} + func (s NewAssetSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_NewAsset, error) { return &g.TransactionStateSnapshot_NewAsset{ AssetId: s.AssetID.Bytes(), @@ -564,6 +734,20 @@ type AssetVolumeSnapshot struct { // AssetVolume in pb func (s AssetVolumeSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetVolume(s) } +func (s AssetVolumeSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AssetVolumeSnapshot) + if !ok { + return false, errors.Errorf("expected AssetVolumeSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.AssetID == other.AssetID && + s.TotalQuantity.Cmp(&other.TotalQuantity) == 0 && s.IsReissuable == other.IsReissuable, nil +} + +func (s AssetVolumeSnapshot) String() string { + return fmt.Sprintf("AssetVolumeSnapshot{AssetID: %s, TotalQuantity: %s, IsReissuable: %t}", + s.AssetID.String(), s.TotalQuantity.String(), s.IsReissuable) +} + func (s AssetVolumeSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_AssetVolume, error) { return &g.TransactionStateSnapshot_AssetVolume{ AssetId: s.AssetID.Bytes(), @@ -603,6 +787,20 @@ type AssetDescriptionSnapshot struct { // AssetNameAndDescription in pb func (s AssetDescriptionSnapshot) Apply(a SnapshotApplier) error { return a.ApplyAssetDescription(s) } +func (s AssetDescriptionSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*AssetDescriptionSnapshot) + if !ok { + return false, errors.Errorf("expected AssetDescriptionSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.AssetID == other.AssetID && s.AssetName == other.AssetName && + s.AssetDescription == other.AssetDescription, nil +} + +func (s AssetDescriptionSnapshot) String() string { + return fmt.Sprintf("AssetDescriptionSnapshot{AssetID: %s, AssetName: %s, AssetDescription: %s}", + s.AssetID.String(), s.AssetName, s.AssetDescription) +} + func (s AssetDescriptionSnapshot) ToProtobuf() (*g.TransactionStateSnapshot_AssetNameAndDescription, error) { return &g.TransactionStateSnapshot_AssetNameAndDescription{ AssetId: s.AssetID.Bytes(), @@ -641,6 +839,18 @@ func (s TransactionStatusSnapshot) Apply(a SnapshotApplier) error { return a.ApplyTransactionsStatus(s) } +func (s TransactionStatusSnapshot) Equal(otherSnapshot AtomicSnapshot) (bool, error) { + other, ok := otherSnapshot.(*TransactionStatusSnapshot) + if !ok { + return false, errors.Errorf("expected TransactionStatusSnapshot, received %s", reflect.TypeOf(otherSnapshot)) + } + return s.Status == other.Status, nil +} + +func (s TransactionStatusSnapshot) String() string { + return fmt.Sprintf("TransactionStatusSnapshot{Status: %s}", s.Status.String()) +} + func (s *TransactionStatusSnapshot) FromProtobuf(p g.TransactionStatus) error { switch p { case g.TransactionStatus_SUCCEEDED: @@ -669,6 +879,36 @@ func (s TransactionStatusSnapshot) AppendToProtobuf(txSnapshots *g.TransactionSt return nil } +type TxSnapshot []AtomicSnapshot + +func (a TxSnapshot) Len() int { return len(a) } +func (a TxSnapshot) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a TxSnapshot) Less(i, j int) bool { + return a[i].String() < a[j].String() +} + +func (a TxSnapshot) Equal(other TxSnapshot) (bool, error) { + if len(a) != len(other) { + return false, nil + } + SortAtomicSnapshotsByType(a) + SortAtomicSnapshotsByType(other) + for i := range a { + equal, err := a[i].Equal(other[i]) + if err != nil { + return false, err + } + if !equal { + return false, nil + } + } + return true, nil +} + +func SortAtomicSnapshotsByType(snapshots TxSnapshot) { + sort.Sort(snapshots) +} + type SnapshotApplier interface { ApplyWavesBalance(snapshot WavesBalanceSnapshot) error ApplyLeaseBalance(snapshot LeaseBalanceSnapshot) error diff --git a/pkg/proto/types.go b/pkg/proto/types.go index 2d7d6d931..7f5b5754e 100644 --- a/pkg/proto/types.go +++ b/pkg/proto/types.go @@ -10,6 +10,7 @@ import ( "io" "math/big" "reflect" + "sort" "strconv" "strings" "time" @@ -2384,6 +2385,27 @@ func NewDataEntryFromValueBytes(valueBytes []byte) (DataEntry, error) { return entry, nil } +func CompareDataEntry(a, b DataEntry) bool { + if a.GetKey() != b.GetKey() || a.GetValueType() != b.GetValueType() { + return false + } + + switch a.GetValueType() { + case DataInteger: + return a.(*IntegerDataEntry).Value == b.(*IntegerDataEntry).Value + case DataBoolean: + return a.(*BooleanDataEntry).Value == b.(*BooleanDataEntry).Value + case DataBinary: + return bytes.Equal(a.(*BinaryDataEntry).Value, b.(*BinaryDataEntry).Value) + case DataString: + return a.(*StringDataEntry).Value == b.(*StringDataEntry).Value + case DataDelete: + return true + default: + return false + } +} + // IntegerDataEntry stores int64 value. type IntegerDataEntry struct { Key string @@ -3173,6 +3195,15 @@ func (e DataEntries) PayloadSize() int { return pl } +func (e DataEntries) String() string { + var resStr string + for _, entry := range e { + entryStr := entry.GetKey() + entry.GetValueType().String() + resStr += entryStr + } + return resStr +} + // BinarySize returns summary binary size of all entries. func (e DataEntries) BinarySize() int { bs := 0 @@ -3219,6 +3250,30 @@ func (e *DataEntries) UnmarshalJSON(data []byte) error { return nil } +// Equal compares two DataEntries slices and returns true if they are equal. +func (e DataEntries) Equal(other DataEntries) (bool, error) { + if len(e) != len(other) { + return false, nil + } + + SortDataEntries(e) + SortDataEntries(other) + + for i := range e { + if !CompareDataEntry(e[i], other[i]) { + return false, nil + } + } + return true, nil +} + +// SortDataEntries sorts DataEntries slice by entry keys. +func SortDataEntries(entries []DataEntry) { + sort.Slice(entries, func(i, j int) bool { + return entries[i].GetKey() < entries[j].GetKey() + }) +} + const scriptPrefix = "base64:" var scriptPrefixBytes = []byte(scriptPrefix) diff --git a/pkg/state/snapshot_storage_internal_test.go b/pkg/state/snapshot_storage_internal_test.go index d2cbea059..3d1f4a90b 100644 --- a/pkg/state/snapshot_storage_internal_test.go +++ b/pkg/state/snapshot_storage_internal_test.go @@ -13,7 +13,7 @@ func TestSaveSnapshots(t *testing.T) { snapshotStor := newSnapshotsAtHeight(storage.hs, storage.settings.AddressSchemeCharacter) ids := genRandBlockIds(t, 1) snapshots := proto.BlockSnapshot{ - TxSnapshots: [][]proto.AtomicSnapshot{{ + TransactionsSnapshots: []proto.TxSnapshot{{ proto.WavesBalanceSnapshot{Address: *generateRandomRecipient(t).Address(), Balance: 100}, proto.WavesBalanceSnapshot{Address: *generateRandomRecipient(t).Address(), Balance: 100}, proto.WavesBalanceSnapshot{Address: *generateRandomRecipient(t).Address(), Balance: 100}, @@ -28,5 +28,5 @@ func TestSaveSnapshots(t *testing.T) { fromStorage, err := snapshotStor.getSnapshots(10) assert.NoError(t, err) - assert.Equal(t, len(fromStorage.TxSnapshots[0]), len(snapshots.TxSnapshots[0])) + assert.Equal(t, len(fromStorage.TransactionsSnapshots[0]), len(snapshots.TransactionsSnapshots[0])) }