Skip to content
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

Subscribe block #203

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
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