-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Get block notifications API #3781
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,6 +51,20 @@ | |
Transactions []json.RawMessage `json:"tx"` | ||
} | ||
|
||
type TrimmedBlock struct { | ||
Header | ||
TxHashes []util.Uint256 | ||
} | ||
|
||
type auxTrimmedBlockOut struct { | ||
TxHashes []util.Uint256 `json:"tx"` | ||
} | ||
|
||
// auxTrimmedBlockIn is used for JSON i/o. | ||
type auxTrimmedBlockIn struct { | ||
TxHashes []json.RawMessage `json:"tx"` | ||
} | ||
Comment on lines
+59
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JSON marshallers are not needed for this structure. DecodeBinary is enough for our purposes. |
||
|
||
// ComputeMerkleRoot computes Merkle tree root hash based on actual block's data. | ||
func (b *Block) ComputeMerkleRoot() util.Uint256 { | ||
hashes := make([]util.Uint256, len(b.Transactions)) | ||
|
@@ -221,7 +235,6 @@ | |
return size | ||
} | ||
|
||
// ToStackItem converts Block to stackitem.Item. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Useless change. |
||
func (b *Block) ToStackItem() stackitem.Item { | ||
items := []stackitem.Item{ | ||
stackitem.NewByteArray(b.Hash().BytesBE()), | ||
|
@@ -241,3 +254,94 @@ | |
|
||
return stackitem.NewArray(items) | ||
} | ||
|
||
// NewTrimmedBlockFromReader creates a block with only the header and transaction hashes. | ||
func NewTrimmedBlockFromReader(stateRootEnabled bool, br *io.BinReader) (*TrimmedBlock, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reuse |
||
block := &TrimmedBlock{ | ||
Header: Header{ | ||
StateRootEnabled: stateRootEnabled, | ||
}, | ||
} | ||
|
||
block.Header.DecodeBinary(br) | ||
lenHashes := br.ReadVarUint() | ||
if lenHashes > MaxTransactionsPerBlock { | ||
return nil, ErrMaxContentsPerBlock | ||
} | ||
if lenHashes > 0 { | ||
block.TxHashes = make([]util.Uint256, lenHashes) | ||
for i := range lenHashes { | ||
block.TxHashes[i].DecodeBinary(br) | ||
} | ||
} | ||
|
||
return block, br.Err | ||
} | ||
|
||
func (b TrimmedBlock) MarshalJSON() ([]byte, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also should be removed, why do we need this code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At first, I considered adding a get trimmed block endpoint. I also had some issues to debug the RPC endpoints (it doesn't stop on the debugger). I had an error that I didn't know where it was coming from. Later I realized that it was due to the notification object serialization, not the block. The same for the 'toStackItem'. I didn't know if that was being used (now I know). I also got confused because the tests 'were passing', when in practice, they weren't. I 'suspected' that this was related to some inner serilization / deserialization that I wasn't aware of. That's why I included these methods. I'll remove this and other parts that aren't being used. |
||
abo := auxTrimmedBlockOut{ | ||
TxHashes: b.TxHashes, | ||
} | ||
|
||
if abo.TxHashes == nil { | ||
abo.TxHashes = []util.Uint256{} | ||
} | ||
auxb, err := json.Marshal(abo) | ||
if err != nil { | ||
return nil, err | ||
} | ||
baseBytes, err := json.Marshal(b.Header) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Does as the 'normal' block does | ||
if baseBytes[len(baseBytes)-1] != '}' || auxb[0] != '{' { | ||
return nil, errors.New("can't merge internal jsons") | ||
} | ||
baseBytes[len(baseBytes)-1] = ',' | ||
baseBytes = append(baseBytes, auxb[1:]...) | ||
return baseBytes, nil | ||
} | ||
|
||
func (b *TrimmedBlock) UnmarshalJSON(data []byte) error { | ||
auxb := new(auxTrimmedBlockIn) | ||
err := json.Unmarshal(data, auxb) | ||
if err != nil { | ||
return err | ||
} | ||
err = json.Unmarshal(data, &b.Header) | ||
if err != nil { | ||
return err | ||
} | ||
if len(auxb.TxHashes) != 0 { | ||
b.TxHashes = make([]util.Uint256, len(auxb.TxHashes)) | ||
for i, hashBytes := range auxb.TxHashes { | ||
err = json.Unmarshal(hashBytes, &b.TxHashes[i]) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (b *TrimmedBlock) ToStackItem() stackitem.Item { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, remove it. We're only interested in proper deserialization from bytes. |
||
items := []stackitem.Item{ | ||
stackitem.NewByteArray(b.Hash().BytesBE()), | ||
stackitem.NewBigInteger(big.NewInt(int64(b.Version))), | ||
stackitem.NewByteArray(b.PrevHash.BytesBE()), | ||
stackitem.NewByteArray(b.MerkleRoot.BytesBE()), | ||
stackitem.NewBigInteger(big.NewInt(int64(b.Timestamp))), | ||
stackitem.NewBigInteger(new(big.Int).SetUint64(b.Nonce)), | ||
stackitem.NewBigInteger(big.NewInt(int64(b.Index))), | ||
stackitem.NewBigInteger(big.NewInt(int64(b.PrimaryIndex))), | ||
stackitem.NewByteArray(b.NextConsensus.BytesBE()), | ||
stackitem.NewBigInteger(big.NewInt(int64(len(b.TxHashes)))), | ||
} | ||
if b.StateRootEnabled { | ||
items = append(items, stackitem.NewByteArray(b.PrevStateRoot.BytesBE())) | ||
} | ||
|
||
return stackitem.NewArray(items) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3149,3 +3149,8 @@ func (bc *Blockchain) GetStoragePrice() int64 { | |
} | ||
return bc.contracts.Policy.GetStoragePriceInternal(bc.dao) | ||
} | ||
|
||
// GetTrimmedBlock returns a block with only the header and transaction hashes. | ||
func (bc *Blockchain) GetTrimmedBlock(hash util.Uint256) (*block.TrimmedBlock, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return |
||
return bc.dao.GetTrimmedBlock(hash) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package result | ||
|
||
import ( | ||
"github.com/nspcc-dev/neo-go/pkg/core/state" | ||
) | ||
|
||
// BlockNotifications represents notifications from a block organized by trigger type. | ||
type BlockNotifications struct { | ||
PrePersistNotifications []state.ContainedNotificationEvent `json:"prepersist,omitempty"` | ||
TxNotifications []state.ContainedNotificationEvent `json:"transactions,omitempty"` | ||
PostPersistNotifications []state.ContainedNotificationEvent `json:"postpersist,omitempty"` | ||
Comment on lines
+7
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/ And I'd request @roman-khimov's opinion on this structure, do we need this explicit separation? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It doesn't, that's the case. But it has There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, OK. Likely we don't care for notifications, if something happened it happened. Container is here anyway. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,6 +112,7 @@ | |
VerifyWitness(util.Uint160, hash.Hashable, *transaction.Witness, int64) (int64, error) | ||
mempool.Feer // fee interface | ||
ContractStorageSeeker | ||
GetTrimmedBlock(hash util.Uint256) (*block.TrimmedBlock, error) | ||
} | ||
|
||
// ContractStorageSeeker is the interface `findstorage*` handlers need to be able to | ||
|
@@ -185,6 +186,14 @@ | |
// Item represents Iterator stackitem. | ||
Item stackitem.Item | ||
} | ||
|
||
notificationComparatorFilter struct { | ||
id neorpc.EventID | ||
filter neorpc.SubscriptionFilter | ||
} | ||
notificationEventContainer struct { | ||
ntf *state.ContainedNotificationEvent | ||
} | ||
Comment on lines
+189
to
+196
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's move it to a separate file. Ref. #3781 (comment). |
||
) | ||
|
||
const ( | ||
|
@@ -219,6 +228,7 @@ | |
"getblockhash": (*Server).getBlockHash, | ||
"getblockheader": (*Server).getBlockHeader, | ||
"getblockheadercount": (*Server).getBlockHeaderCount, | ||
"getblocknotifications": (*Server).getBlockNotifications, | ||
AnnaShaleva marked this conversation as resolved.
Show resolved
Hide resolved
AnnaShaleva marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"getblocksysfee": (*Server).getBlockSysFee, | ||
"getcandidates": (*Server).getCandidates, | ||
"getcommittee": (*Server).getCommittee, | ||
|
@@ -3202,3 +3212,51 @@ | |
} | ||
return tx.Bytes(), nil | ||
} | ||
|
||
// getBlockNotifications returns notifications from a specific block with optional filtering. | ||
func (s *Server) getBlockNotifications(reqParams params.Params) (any, *neorpc.Error) { | ||
param := reqParams.Value(0) | ||
hash, respErr := s.blockHashFromParam(param) | ||
if respErr != nil { | ||
return nil, respErr | ||
} | ||
|
||
block, err := s.chain.GetTrimmedBlock(hash) | ||
if err != nil { | ||
return nil, neorpc.ErrUnknownBlock | ||
} | ||
|
||
var filter *neorpc.NotificationFilter | ||
if len(reqParams) > 1 { | ||
filter = new(neorpc.NotificationFilter) | ||
err := json.Unmarshal(reqParams[1].RawMessage, filter) | ||
if err != nil { | ||
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid filter: %s", err)) | ||
} | ||
if err := filter.IsValid(); err != nil { | ||
return nil, neorpc.WrapErrorWithData(neorpc.ErrInvalidParams, fmt.Sprintf("invalid filter: %s", err)) | ||
} | ||
} | ||
|
||
notifications := &result.BlockNotifications{} | ||
|
||
aers, err := s.chain.GetAppExecResults(block.Hash(), trigger.OnPersist) | ||
if err == nil && len(aers) > 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return this error if it's not nil. Ref. #3781 (comment). |
||
notifications.PrePersistNotifications = processAppExecResults([]state.AppExecResult{aers[0]}, filter) | ||
} | ||
|
||
for _, txHash := range block.TxHashes { | ||
aers, err := s.chain.GetAppExecResults(txHash, trigger.Application) | ||
if err != nil { | ||
return nil, neorpc.NewInternalServerError("failed to get app exec results") | ||
} | ||
notifications.TxNotifications = append(notifications.TxNotifications, processAppExecResults(aers, filter)...) | ||
} | ||
|
||
aers, err = s.chain.GetAppExecResults(block.Hash(), trigger.PostPersist) | ||
if err == nil && len(aers) > 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto. |
||
notifications.PostPersistNotifications = processAppExecResults([]state.AppExecResult{aers[0]}, filter) | ||
} | ||
|
||
return notifications, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, preserve common line width (~80 symbols).