@@ -14,6 +14,7 @@ import (
14
14
"github.com/nspcc-dev/neo-go/pkg/core/state"
15
15
"github.com/nspcc-dev/neo-go/pkg/core/storage"
16
16
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
17
+ "github.com/nspcc-dev/neo-go/pkg/encoding/address"
17
18
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
18
19
"github.com/nspcc-dev/neo-go/pkg/io"
19
20
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
@@ -610,14 +611,15 @@ func (dao *Simple) GetTransaction(hash util.Uint256) (*transaction.Transaction,
610
611
if err != nil {
611
612
return nil , 0 , err
612
613
}
613
- if len (b ) < 6 {
614
+ if len (b ) < 1 {
614
615
return nil , 0 , errors .New ("bad transaction bytes" )
615
616
}
616
617
if b [0 ] != storage .ExecTransaction {
617
618
// It may be a block.
618
619
return nil , 0 , storage .ErrKeyNotFound
619
620
}
620
- if b [5 ] == transaction .DummyVersion {
621
+ if len (b ) == 1 + 4 { // storage.ExecTransaction + index
622
+ // It's a conflict record stub.
621
623
return nil , 0 , storage .ErrKeyNotFound
622
624
}
623
625
r := io .NewBinReaderFromBuf (b )
@@ -686,46 +688,52 @@ func (dao *Simple) StoreHeaderHashes(hashes []util.Uint256, height uint32) error
686
688
// or in the list of conflicting transactions. If non-zero signers are specified,
687
689
// then additional check against the conflicting transaction signers intersection
688
690
// is held. Do not omit signers in case if it's important to check the validity
689
- // of a supposedly conflicting on-chain transaction.
690
- func (dao * Simple ) HasTransaction (hash util.Uint256 , signers []transaction.Signer ) error {
691
+ // of a supposedly conflicting on-chain transaction. The retrieved conflict isn't
692
+ // checked against the maxTraceableBlocks setting if signers are omitted.
693
+ // HasTransaction does not consider the case of block executable.
694
+ func (dao * Simple ) HasTransaction (hash util.Uint256 , signers []transaction.Signer , currentIndex uint32 , maxTraceableBlocks uint32 ) error {
691
695
key := dao .makeExecutableKey (hash )
692
696
bytes , err := dao .Store .Get (key )
693
697
if err != nil {
694
698
return nil
695
699
}
696
700
697
- if len (bytes ) < 6 {
701
+ if len (bytes ) < 5 { // (storage.ExecTransaction + index) for conflict record
698
702
return nil
699
703
}
700
- if bytes [ 5 ] != transaction . DummyVersion {
701
- return ErrAlreadyExists
704
+ if len ( bytes ) != 5 {
705
+ return ErrAlreadyExists // fully-qualified transaction
702
706
}
703
707
if len (signers ) == 0 {
704
708
return ErrHasConflicts
705
709
}
706
710
707
- sMap := make (map [util.Uint160 ]struct {}, len (signers ))
711
+ index := binary .LittleEndian .Uint32 (bytes [1 :])
712
+ if ! isTraceableBlock (index , currentIndex , maxTraceableBlocks ) {
713
+ // The most fresh conflict record is already outdated.
714
+ return nil
715
+ }
716
+
717
+ sKey := make ([]byte , len (key )+ util .Uint160Size )
718
+ copy (sKey , key )
708
719
for _ , s := range signers {
709
- sMap [s .Account ] = struct {}{}
710
- }
711
- br := io .NewBinReaderFromBuf (bytes [6 :])
712
- for {
713
- var u util.Uint160
714
- u .DecodeBinary (br )
715
- if br .Err != nil {
716
- if errors .Is (br .Err , iocore .EOF ) {
717
- break
720
+ copy (sKey [len (key ):], s .Account .BytesBE ())
721
+ v , err := dao .Store .Get (sKey )
722
+ if err == nil {
723
+ index = binary .LittleEndian .Uint32 (v [1 :])
724
+ if isTraceableBlock (index , currentIndex , maxTraceableBlocks ) {
725
+ return ErrHasConflicts
718
726
}
719
- return fmt .Errorf ("failed to decode conflict record: %w" , err )
720
- }
721
- if _ , ok := sMap [u ]; ok {
722
- return ErrHasConflicts
723
727
}
724
728
}
725
729
726
730
return nil
727
731
}
728
732
733
+ func isTraceableBlock (index , height , maxTraceableBlocks uint32 ) bool {
734
+ return index <= height && index + maxTraceableBlocks > height
735
+ }
736
+
729
737
// StoreAsBlock stores given block as DataBlock. It can reuse given buffer for
730
738
// the purpose of value serialization.
731
739
func (dao * Simple ) StoreAsBlock (block * block.Block , aer1 * state.AppExecResult , aer2 * state.AppExecResult ) error {
@@ -768,7 +776,32 @@ func (dao *Simple) DeleteBlock(h util.Uint256) error {
768
776
for _ , attr := range tx .GetAttributes (transaction .ConflictsT ) {
769
777
hash := attr .Value .(* transaction.Conflicts ).Hash
770
778
copy (key [1 :], hash .BytesBE ())
771
- dao .Store .Delete (key )
779
+
780
+ v , err := dao .Store .Get (key )
781
+ if err != nil {
782
+ return fmt .Errorf ("failed to retrieve conflict record stub for %s (height %d, conflict %s): %w" , tx .Hash ().StringLE (), b .Index , hash .StringLE (), err )
783
+ }
784
+ index := binary .LittleEndian .Uint32 (v [1 :])
785
+ // We can check for `<=` here, but use equality comparison to be more precise
786
+ // and do not touch earlier conflict records (if any). Their removal must be triggered
787
+ // by the caller code.
788
+ if index == b .Index {
789
+ dao .Store .Delete (key )
790
+ }
791
+
792
+ sKey := make ([]byte , len (key )+ util .Uint160Size )
793
+ copy (sKey , key )
794
+ for _ , s := range tx .Signers {
795
+ copy (sKey [len (key ):], s .Account .BytesBE ())
796
+ v , err := dao .Store .Get (sKey )
797
+ if err != nil {
798
+ return fmt .Errorf ("failed to retrieve conflict record for %s (height %d, conflict %s, signer %s): %w" , tx .Hash ().StringLE (), b .Index , hash .StringLE (), address .Uint160ToString (s .Account ), err )
799
+ }
800
+ index = binary .LittleEndian .Uint32 (v [1 :])
801
+ if index == b .Index {
802
+ dao .Store .Delete (sKey )
803
+ }
804
+ }
772
805
}
773
806
}
774
807
@@ -826,51 +859,23 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32,
826
859
if buf .Err != nil {
827
860
return buf .Err
828
861
}
829
- dao .Store .Put (key , buf .Bytes ())
862
+ val := buf .Bytes ()
863
+ dao .Store .Put (key , val )
830
864
831
- var (
832
- valuePrefix []byte
833
- newSigners []byte
834
- )
835
865
attrs := tx .GetAttributes (transaction .ConflictsT )
836
866
for _ , attr := range attrs {
867
+ // Conflict record stub.
837
868
hash := attr .Value .(* transaction.Conflicts ).Hash
838
869
copy (key [1 :], hash .BytesBE ())
839
-
840
- old , err := dao .Store .Get (key )
841
- if err != nil && ! errors .Is (err , storage .ErrKeyNotFound ) {
842
- return fmt .Errorf ("failed to retrieve previous conflict record for %s: %w" , hash .StringLE (), err )
843
- }
844
- if err == nil {
845
- if len (old ) <= 6 { // storage.ExecTransaction + U32LE index + transaction.DummyVersion
846
- return fmt .Errorf ("invalid conflict record format of length %d" , len (old ))
847
- }
848
- }
849
- buf .Reset ()
850
- buf .WriteBytes (old )
851
- if len (old ) == 0 {
852
- if len (valuePrefix ) != 0 {
853
- buf .WriteBytes (valuePrefix )
854
- } else {
855
- buf .WriteB (storage .ExecTransaction )
856
- buf .WriteU32LE (index )
857
- buf .WriteB (transaction .DummyVersion )
858
- }
859
- }
860
- newSignersOffset := buf .Len ()
861
- if len (newSigners ) == 0 {
862
- for _ , s := range tx .Signers {
863
- s .Account .EncodeBinary (buf .BinWriter )
864
- }
865
- } else {
866
- buf .WriteBytes (newSigners )
867
- }
868
- val := buf .Bytes ()
870
+ val = slice .Copy (val [:1 + 4 ]) // storage.ExecTransaction + index
869
871
dao .Store .Put (key , val )
870
872
871
- if len (attrs ) > 1 && len (valuePrefix ) == 0 {
872
- valuePrefix = slice .Copy (val [:6 ])
873
- newSigners = slice .Copy (val [newSignersOffset :])
873
+ // Conflicting signers.
874
+ sKey := make ([]byte , len (key )+ util .Uint160Size )
875
+ copy (sKey , key )
876
+ for _ , s := range tx .Signers {
877
+ copy (sKey [len (key ):], s .Account .BytesBE ())
878
+ dao .Store .Put (sKey , val )
874
879
}
875
880
}
876
881
return nil
0 commit comments