Skip to content

Commit

Permalink
Subscribe block (#203)
Browse files Browse the repository at this point in the history
* update

* udpate

* update

* updat

* update

* update

* update
  • Loading branch information
blockchain-develop authored Oct 10, 2024
1 parent 525369e commit 1af28d5
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 9 deletions.
78 changes: 77 additions & 1 deletion rpc/getBlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (cl *Client) GetBlockWithOpts(
opts.Encoding,
// Valid encodings:
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
Expand Down Expand Up @@ -161,3 +161,79 @@ type GetBlockResult struct {
// The number of blocks beneath this block.
BlockHeight *uint64 `json:"blockHeight"`
}

func (cl *Client) GetParsedBlockWithOpts(
ctx context.Context,
slot uint64,
opts *GetBlockOpts,
) (out *GetParsedBlockResult, err error) {

obj := M{
"encoding": solana.EncodingJSONParsed,
}

if opts != nil {
if opts.TransactionDetails != "" {
obj["transactionDetails"] = opts.TransactionDetails
}
if opts.Rewards != nil {
obj["rewards"] = opts.Rewards
}
if opts.Commitment != "" {
obj["commitment"] = opts.Commitment
}
if opts.MaxSupportedTransactionVersion != nil {
obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion
}
}

params := []interface{}{slot, obj}

err = cl.rpcClient.CallForInto(ctx, &out, "getBlock", params)

if err != nil {
return nil, err
}
if out == nil {
// Block is not confirmed.
return nil, ErrNotConfirmed
}
return
}

type GetParsedBlockResult struct {
// The blockhash of this block.
Blockhash solana.Hash `json:"blockhash"`

// The blockhash of this block's parent;
// if the parent block is not available due to ledger cleanup,
// this field will return "11111111111111111111111111111111".
PreviousBlockhash solana.Hash `json:"previousBlockhash"`

// The slot index of this block's parent.
ParentSlot uint64 `json:"parentSlot"`

// Present if "full" transaction details are requested.
Transactions []ParsedTransactionWithMeta `json:"transactions"`

// Present if "signatures" are requested for transaction details;
// an array of signatures, corresponding to the transaction order in the block.
Signatures []solana.Signature `json:"signatures"`

// Present if rewards are requested.
Rewards []BlockReward `json:"rewards"`

// Estimated production time, as Unix timestamp (seconds since the Unix epoch).
// Nil if not available.
BlockTime *solana.UnixTimeSeconds `json:"blockTime"`

// The number of blocks beneath this block.
BlockHeight *uint64 `json:"blockHeight"`
}

type ParsedTransactionWithMeta struct {
Slot uint64
BlockTime *solana.UnixTimeSeconds
Transaction *ParsedTransaction
Meta *ParsedTransactionMeta
}
2 changes: 1 addition & 1 deletion rpc/getTransaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (cl *Client) GetTransaction(
opts.Encoding,
// Valid encodings:
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
Expand Down
13 changes: 7 additions & 6 deletions rpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ type TransactionMeta struct {
Rewards []BlockReward `json:"rewards"`

LoadedAddresses LoadedAddresses `json:"loadedAddresses"`

ComputeUnitsConsumed *uint64 `json:"computeUnitsConsumed"`
}

Expand Down Expand Up @@ -498,11 +498,12 @@ type ParsedMessage struct {
}

type ParsedInstruction struct {
Program string `json:"program,omitempty"`
ProgramId solana.PublicKey `json:"programId,omitempty"`
Parsed *InstructionInfoEnvelope `json:"parsed,omitempty"`
Data solana.Base58 `json:"data,omitempty"`
Accounts []solana.PublicKey `json:"accounts,omitempty"`
Program string `json:"program,omitempty"`
ProgramId solana.PublicKey `json:"programId,omitempty"`
Parsed *InstructionInfoEnvelope `json:"parsed,omitempty"`
Data solana.Base58 `json:"data,omitempty"`
Accounts []solana.PublicKey `json:"accounts,omitempty"`
StackHeight int `json:"stackHeight"`
}

type InstructionInfoEnvelope struct {
Expand Down
2 changes: 1 addition & 1 deletion rpc/ws/blockSubscribe.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (cl *Client) BlockSubscribe(
opts.Encoding,
// Valid encodings:
// solana.EncodingJSON, // TODO
// solana.EncodingJSONParsed, // TODO
solana.EncodingJSONParsed, // TODO
solana.EncodingBase58,
solana.EncodingBase64,
solana.EncodingBase64Zstd,
Expand Down
123 changes: 123 additions & 0 deletions rpc/ws/parsedBlockSubscribe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2022 github.com/gagliardetto
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ws

import (
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
)

type ParsedBlockResult struct {
Context struct {
Slot uint64
} `json:"context"`
Value struct {
Slot uint64 `json:"slot"`
Err interface{} `json:"err,omitempty"`
Block *rpc.GetParsedBlockResult `json:"block,omitempty"`
} `json:"value"`
}

// NOTE: Unstable, disabled by default
//
// Subscribe to receive notification anytime a new block is Confirmed or Finalized.
//
// **This subscription is unstable and only available if the validator was started
// with the `--rpc-pubsub-enable-block-subscription` flag. The format of this
// subscription may change in the future**
func (cl *Client) ParsedBlockSubscribe(
filter BlockSubscribeFilter,
opts *BlockSubscribeOpts,
) (*ParsedBlockSubscription, error) {
var params []interface{}
if filter != nil {
switch v := filter.(type) {
case BlockSubscribeFilterAll:
params = append(params, "all")
case *BlockSubscribeFilterMentionsAccountOrProgram:
params = append(params, rpc.M{"mentionsAccountOrProgram": v.Pubkey})
}
}
if opts != nil {
obj := make(rpc.M)
if opts.Commitment != "" {
obj["commitment"] = opts.Commitment
}
obj["encoding"] = solana.EncodingJSONParsed
if opts.TransactionDetails != "" {
obj["transactionDetails"] = opts.TransactionDetails
}
if opts.Rewards != nil {
obj["rewards"] = opts.Rewards
}
if opts.MaxSupportedTransactionVersion != nil {
obj["maxSupportedTransactionVersion"] = *opts.MaxSupportedTransactionVersion
}
if len(obj) > 0 {
params = append(params, obj)
}
}
genSub, err := cl.subscribe(
params,
nil,
"blockSubscribe",
"blockUnsubscribe",
func(msg []byte) (interface{}, error) {
var res ParsedBlockResult
err := decodeResponseFromMessage(msg, &res)
return &res, err
},
)
if err != nil {
return nil, err
}
return &ParsedBlockSubscription{
sub: genSub,
}, nil
}

type ParsedBlockSubscription struct {
sub *Subscription
}

func (sw *ParsedBlockSubscription) Recv() (*ParsedBlockResult, error) {
select {
case d := <-sw.sub.stream:
return d.(*ParsedBlockResult), nil
case err := <-sw.sub.err:
return nil, err
}
}

func (sw *ParsedBlockSubscription) Err() <-chan error {
return sw.sub.err
}

func (sw *ParsedBlockSubscription) Response() <-chan *ParsedBlockResult {
typedChan := make(chan *ParsedBlockResult, 1)
go func(ch chan *ParsedBlockResult) {
// TODO: will this subscription yield more than one result?
d, ok := <-sw.sub.stream
if !ok {
return
}
ch <- d.(*ParsedBlockResult)
}(typedChan)
return typedChan
}

func (sw *ParsedBlockSubscription) Unsubscribe() {
sw.sub.Unsubscribe()
}
63 changes: 63 additions & 0 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ func MustTransactionFromDecoder(decoder *bin.Decoder) *Transaction {
return out
}

const (
AccountsTypeIndex = "Fee"
AccountsTypeKey = "Key"
)

type CompiledInstruction struct {
// Index into the message.accountKeys array indicating the program account that executes this instruction.
// NOTE: it is actually a uint8, but using a uint16 because uint8 is treated as a byte everywhere,
Expand All @@ -128,10 +133,68 @@ type CompiledInstruction struct {
// List of ordered indices into the message.accountKeys array indicating which accounts to pass to the program.
// NOTE: it is actually a []uint8, but using a uint16 because []uint8 is treated as a []byte everywhere,
// and that can be an issue.
AccountsWithKey []PublicKey `json:"omitempty"`
//
Accounts []uint16 `json:"accounts"`

// The program input data encoded in a base-58 string.
Data Base58 `json:"data"`

//
StackHeight int `json:"stackHeight"`
}

type compiledInstruction struct {
// Index into the message.accountKeys array indicating the program account that executes this instruction.
// NOTE: it is actually a uint8, but using a uint16 because uint8 is treated as a byte everywhere,
// and that can be an issue.
ProgramIDIndex uint16 `json:"programIdIndex"`

// List of ordered indices into the message.accountKeys array indicating which accounts to pass to the program.
// NOTE: it is actually a []uint8, but using a uint16 because []uint8 is treated as a []byte everywhere,
// and that can be an issue.
Accounts []interface{} `json:"accounts"`

// The program input data encoded in a base-58 string.
Data Base58 `json:"data"`

//
StackHeight int `json:"stackHeight"`
}

func (ci *CompiledInstruction) MarshalJSON() ([]byte, error) {
return json.Marshal(ci)
}

func (ci *CompiledInstruction) UnmarshalJSON(data []byte) error {
in := compiledInstruction{}
err := json.Unmarshal(data, &in)
if err != nil {
return err
}
//
ci.ProgramIDIndex = in.ProgramIDIndex
ci.Data = in.Data
ci.StackHeight = in.StackHeight
ci.Accounts = make([]uint16, 0)
if len(in.Accounts) == 0 {
return nil
}
_, ok := in.Accounts[0].(string)
if ok {
accountsWithKey := make([]PublicKey, len(in.Accounts))
for i, item := range in.Accounts {
accountsWithKey[i] = MustPublicKeyFromBase58(item.(string))
}
ci.AccountsWithKey = accountsWithKey
} else {
AccountsWithIndex := make([]uint16, len(in.Accounts))
for i, item := range in.Accounts {
AccountsWithIndex[i] = uint16(item.(float64))
}
ci.Accounts = AccountsWithIndex
}
return nil
}

func (ci *CompiledInstruction) ResolveInstructionAccounts(message *Message) ([]*AccountMeta, error) {
Expand Down

0 comments on commit 1af28d5

Please sign in to comment.