@@ -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,48 @@ 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
+ if ! isTraceableBlock (bytes [1 :], currentIndex , maxTraceableBlocks ) {
712
+ // The most fresh conflict record is already outdated.
713
+ return nil
714
+ }
715
+
708
716
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
717
+ v , err := dao .Store .Get (append (key , s .Account .BytesBE ()... ))
718
+ if err == nil {
719
+ if isTraceableBlock (v [1 :], currentIndex , maxTraceableBlocks ) {
720
+ return ErrHasConflicts
718
721
}
719
- return fmt .Errorf ("failed to decode conflict record: %w" , err )
720
- }
721
- if _ , ok := sMap [u ]; ok {
722
- return ErrHasConflicts
723
722
}
724
723
}
725
724
726
725
return nil
727
726
}
728
727
728
+ func isTraceableBlock (indexBytes []byte , height , maxTraceableBlocks uint32 ) bool {
729
+ index := binary .LittleEndian .Uint32 (indexBytes [1 :])
730
+ return index <= height && index + maxTraceableBlocks > height
731
+ }
732
+
729
733
// StoreAsBlock stores given block as DataBlock. It can reuse given buffer for
730
734
// the purpose of value serialization.
731
735
func (dao * Simple ) StoreAsBlock (block * block.Block , aer1 * state.AppExecResult , aer2 * state.AppExecResult ) error {
@@ -768,7 +772,30 @@ func (dao *Simple) DeleteBlock(h util.Uint256) error {
768
772
for _ , attr := range tx .GetAttributes (transaction .ConflictsT ) {
769
773
hash := attr .Value .(* transaction.Conflicts ).Hash
770
774
copy (key [1 :], hash .BytesBE ())
771
- dao .Store .Delete (key )
775
+
776
+ v , err := dao .Store .Get (key )
777
+ if err != nil {
778
+ return fmt .Errorf ("failed to retrieve conflict record stub for %s (height %d, conflict %s): %w" , tx .Hash ().StringLE (), b .Index , hash .StringLE (), err )
779
+ }
780
+ index := binary .LittleEndian .Uint32 (v [1 :])
781
+ // We can check for `<=` here, but use equality comparison to be more precise
782
+ // and do not touch earlier conflict records (if any). Their removal must be triggered
783
+ // by the caller code.
784
+ if index == b .Index {
785
+ dao .Store .Delete (key )
786
+ }
787
+
788
+ for _ , s := range tx .Signers {
789
+ sKey := append (key , s .Account .BytesBE ()... )
790
+ v , err := dao .Store .Get (sKey )
791
+ if err != nil {
792
+ 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 )
793
+ }
794
+ index = binary .LittleEndian .Uint32 (v [1 :])
795
+ if index == b .Index {
796
+ dao .Store .Delete (sKey )
797
+ }
798
+ }
772
799
}
773
800
}
774
801
@@ -826,51 +853,20 @@ func (dao *Simple) StoreAsTransaction(tx *transaction.Transaction, index uint32,
826
853
if buf .Err != nil {
827
854
return buf .Err
828
855
}
829
- dao .Store .Put (key , buf .Bytes ())
856
+ val := buf .Bytes ()
857
+ dao .Store .Put (key , val )
830
858
831
- var (
832
- valuePrefix []byte
833
- newSigners []byte
834
- )
859
+ val = val [:5 ] // storage.ExecTransaction (1 byte) + index (4 bytes)
835
860
attrs := tx .GetAttributes (transaction .ConflictsT )
836
861
for _ , attr := range attrs {
862
+ // Conflict record stub.
837
863
hash := attr .Value .(* transaction.Conflicts ).Hash
838
864
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 ()
869
865
dao .Store .Put (key , val )
870
866
871
- if len ( attrs ) > 1 && len ( valuePrefix ) == 0 {
872
- valuePrefix = slice . Copy ( val [: 6 ])
873
- newSigners = slice . Copy ( val [ newSignersOffset :] )
867
+ // Conflicting signers.
868
+ for _ , s := range tx . Signers {
869
+ dao . Store . Put ( append ( key , s . Account . BytesBE () ... ), val )
874
870
}
875
871
}
876
872
return nil
0 commit comments