Skip to content

Commit

Permalink
Fix jsonParsed
Browse files Browse the repository at this point in the history
  • Loading branch information
gagliardetto committed May 17, 2024
1 parent 7aa5ed8 commit 74ef21c
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 123 deletions.
4 changes: 2 additions & 2 deletions multiepoch-getBlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,16 @@ func (multi *MultiEpoch) handleGetBlock(ctx context.Context, conn *requestContex
} else {
txResp.Version = "legacy"
}
txResp.Meta = meta

encodedTx, err := encodeTransactionResponseBasedOnWantedEncoding(*params.Options.Encoding, tx, meta)
encodedTx, encodedMeta, err := encodeTransactionResponseBasedOnWantedEncoding(*params.Options.Encoding, tx, meta)
if err != nil {
return &jsonrpc2.Error{
Code: jsonrpc2.CodeInternalError,
Message: "Internal error",
}, fmt.Errorf("failed to encode transaction: %v", err)
}
txResp.Transaction = encodedTx
txResp.Meta = encodedMeta
}

allTransactions = append(allTransactions, txResp)
Expand Down
4 changes: 2 additions & 2 deletions multiepoch-getTransaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,16 +191,16 @@ func (multi *MultiEpoch) handleGetTransaction(ctx context.Context, conn *request
} else {
response.Version = "legacy"
}
response.Meta = meta

encodedTx, err := encodeTransactionResponseBasedOnWantedEncoding(*params.Options.Encoding, tx, meta)
encodedTx, encodedMeta, err := encodeTransactionResponseBasedOnWantedEncoding(*params.Options.Encoding, tx, meta)
if err != nil {
return &jsonrpc2.Error{
Code: jsonrpc2.CodeInternalError,
Message: "Internal error",
}, fmt.Errorf("failed to encode transaction: %w", err)
}
response.Transaction = encodedTx
response.Meta = encodedMeta
}

// reply with the data
Expand Down
264 changes: 145 additions & 119 deletions request-response.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,52 +377,99 @@ func parseGetTransactionRequest(raw *json.RawMessage) (*GetTransactionRequest, e

var zstdEncoderPool = zstdpool.NewEncoderPool()

func compiledInstructionsToJsonParsed(
tx solana.Transaction,
inst solana.CompiledInstruction,
meta any,
) (json.RawMessage, error) {
programId, err := tx.ResolveProgramIDIndex(inst.ProgramIDIndex)
if err != nil {
return nil, fmt.Errorf("failed to resolve program ID index: %w", err)
}
keys := tx.Message.AccountKeys
instrParams := txstatus.Parameters{
ProgramID: programId,
Instruction: txstatus.CompiledInstruction{
ProgramIDIndex: uint8(inst.ProgramIDIndex),
Accounts: func() []uint8 {
out := make([]uint8, len(inst.Accounts))
for i, v := range inst.Accounts {
out[i] = uint8(v)
}
return out
}(),
Data: inst.Data,
},
AccountKeys: txstatus.AccountKeys{
StaticKeys: func() []solana.PublicKey {
return clone(keys)
}(),
// TODO: test this:
DynamicKeys: func() *txstatus.LoadedAddresses {
switch vv := meta.(type) {
case *confirmed_block.TransactionStatusMeta:
return &txstatus.LoadedAddresses{
Writable: func() []solana.PublicKey {
return byteSlicesToKeySlices(vv.LoadedWritableAddresses)
}(),
Readonly: func() []solana.PublicKey {
return byteSlicesToKeySlices(vv.LoadedReadonlyAddresses)
}(),
}
default:
return nil
}
}(),
},
StackHeight: nil,
}

parsedInstructionJSON, err := instrParams.ParseInstruction()
if err != nil || parsedInstructionJSON == nil || !strings.HasPrefix(strings.TrimSpace(string(parsedInstructionJSON)), "{") {
nonParseadInstructionJSON := map[string]any{
"accounts": func() []string {
out := make([]string, len(inst.Accounts))
for i, v := range inst.Accounts {
out[i] = tx.Message.AccountKeys[v].String()
}
return out
}(),
"data": base58.Encode(inst.Data),
"programId": programId.String(),
"stackHeight": nil,
}
asRaw, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(nonParseadInstructionJSON)
return asRaw, nil
} else {
return parsedInstructionJSON, nil
}
}

func encodeTransactionResponseBasedOnWantedEncoding(
encoding solana.EncodingType,
tx solana.Transaction,
meta any,
) (any, error) {
) (any, any, error) {
switch encoding {
case solana.EncodingBase58, solana.EncodingBase64, solana.EncodingBase64Zstd:
txBuf, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal transaction: %w", err)
return nil, nil, fmt.Errorf("failed to marshal transaction: %w", err)
}
return encodeBytesResponseBasedOnWantedEncoding(encoding, txBuf)
tOut, err := encodeBytesResponseBasedOnWantedEncoding(encoding, txBuf)
return tOut, meta, err
case solana.EncodingJSONParsed:
if !txstatus.IsEnabled() {
return nil, fmt.Errorf("unsupported encoding")
return nil, nil, fmt.Errorf("unsupported encoding")
}

ixIndexToNumAccountStaticDynamicWR := make(map[int][3]uint8)
numStaticAccountsForTx := (len(tx.Message.AccountKeys))
{
meta, ok := meta.(*confirmed_block.TransactionStatusMeta)
unwrappedMeta, ok := meta.(*confirmed_block.TransactionStatusMeta)
if ok {
numDynamicWritable := len(meta.LoadedWritableAddresses)
for i, inst := range tx.Message.Instructions {
vvv := [3]uint8{
0,
0,
0,
}
for _, accountIndex := range toUniqueSorted(inst.Accounts) {
if accountIndex < uint16(numStaticAccountsForTx) {
vvv[0]++
} else {
if accountIndex < uint16(numStaticAccountsForTx+numDynamicWritable) {
vvv[1]++
} else {
vvv[2]++
}
}
}
ixIndexToNumAccountStaticDynamicWR[i] = vvv
}
{
tables := map[solana.PublicKey]solana.PublicKeySlice{}
writable := byteSlicesToKeySlices(meta.LoadedWritableAddresses)
readonly := byteSlicesToKeySlices(meta.LoadedReadonlyAddresses)
writable := byteSlicesToKeySlices(unwrappedMeta.LoadedWritableAddresses)
readonly := byteSlicesToKeySlices(unwrappedMeta.LoadedReadonlyAddresses)
for _, addr := range tx.Message.AddressTableLookups {
numTakeWritable := len(addr.WritableIndexes)
numTakeReadonly := len(addr.ReadonlyIndexes)
Expand Down Expand Up @@ -463,7 +510,7 @@ func encodeTransactionResponseBasedOnWantedEncoding(
}
err := tx.Message.SetAddressTables(tables)
if err != nil {
return nil, fmt.Errorf("failed to set address tables: %w", err)
return nil, nil, fmt.Errorf("failed to set address tables: %w", err)
}
}
if tx.Message.IsVersioned() {
Expand All @@ -472,114 +519,93 @@ func encodeTransactionResponseBasedOnWantedEncoding(
panic(err)
}
}
} else {
for i, inst := range tx.Message.Instructions {
vvv := [3]uint8{
uint8(len(toUniqueSorted(inst.Accounts))),
0,
0,
}
ixIndexToNumAccountStaticDynamicWR[i] = vvv
}
}
}

parsedInstructions := make([]json.RawMessage, 0)

for instIndex, inst := range tx.Message.Instructions {
programId, _ := tx.ResolveProgramIDIndex(inst.ProgramIDIndex)
keys := tx.Message.AccountKeys
numStaticThisInstruction := ixIndexToNumAccountStaticDynamicWR[instIndex][0]
numDynamicWritableThisInstruction := ixIndexToNumAccountStaticDynamicWR[instIndex][1]
numDynamicReadonlyThisInstruction := ixIndexToNumAccountStaticDynamicWR[instIndex][2]
instrParams := txstatus.Parameters{
ProgramID: programId,
Instruction: txstatus.CompiledInstruction{
ProgramIDIndex: uint8(inst.ProgramIDIndex),
Accounts: func() []uint8 {
out := make([]uint8, len(inst.Accounts))
for i, v := range inst.Accounts {
out[i] = uint8(v)
for _, inst := range tx.Message.Instructions {
parsedInstructionJSON, err := compiledInstructionsToJsonParsed(tx, inst, meta)
if err != nil {
return nil, nil, fmt.Errorf("failed to compile instruction: %w", err)
}
parsedInstructions = append(parsedInstructions, parsedInstructionJSON)
}

resp, err := txstatus.FromTransaction(tx)
if err != nil {
return nil, nil, fmt.Errorf("failed to convert transaction to txstatus.Transaction: %w", err)
}
resp.Message.Instructions = parsedInstructions

{
// now try to encode unwrappedMeta:
unwrappedMeta, ok := meta.(*confirmed_block.TransactionStatusMeta)
if ok {
// convert meta to json:
metaJSON, err := toMapAny(unwrappedMeta)
if err != nil {
return nil, nil, fmt.Errorf("failed to marshal meta: %w", err)
}
for innerIndex, insts := range unwrappedMeta.InnerInstructions {
inner := make([]solana.CompiledInstruction, len(insts.Instructions))
for j, inst := range insts.Instructions {
inner[j] = solana.CompiledInstruction{
ProgramIDIndex: uint16(inst.ProgramIdIndex),
Accounts: byeSliceToUint16Slice(inst.Accounts),
Data: clone(inst.Data),
}
return out
}(),
Data: inst.Data,
},
AccountKeys: txstatus.AccountKeys{
StaticKeys: func() []solana.PublicKey {
out := make([]solana.PublicKey, numStaticThisInstruction)
if numStaticThisInstruction == 0 {
return out
}
for instIndex, inst := range inner {
parsedInstructionJSON, err := compiledInstructionsToJsonParsed(tx, inst, unwrappedMeta)
if err != nil {
return nil, nil, fmt.Errorf("failed to compile instruction: %w", err)
}
staticKeysForInstruction := keys[:numStaticThisInstruction]
copy(out, staticKeysForInstruction)
return out
}(),
// TODO: test this:
DynamicKeys: func() *txstatus.LoadedAddresses {
switch meta.(type) {
case *confirmed_block.TransactionStatusMeta:
return &txstatus.LoadedAddresses{
Writable: func() []solana.PublicKey {
writable := make([]solana.PublicKey, numDynamicWritableThisInstruction)
if numDynamicWritableThisInstruction == 0 {
return writable
}
copy(writable, keys[numStaticThisInstruction:numStaticThisInstruction+numDynamicWritableThisInstruction])
return writable
}(),
Readonly: func() []solana.PublicKey {
readonly := make([]solana.PublicKey, numDynamicReadonlyThisInstruction)
if numDynamicReadonlyThisInstruction == 0 {
return readonly
// now replace the inner instruction with the parsed instruction:
{
if _, ok := metaJSON["inner_instructions"]; !ok {
metaJSON["inner_instructions"] = []any{}
} else {
innerInstructions, ok := metaJSON["inner_instructions"].([]any)
if ok && len(innerInstructions) > innerIndex {
relevantInner := innerInstructions[innerIndex].(map[string]any)
{
_, ok := relevantInner["instructions"].([]any)
if ok {
metaJSON["inner_instructions"].([]any)[innerIndex].(map[string]any)["instructions"].([]any)[instIndex] = parsedInstructionJSON
}
}
copy(readonly, keys[numStaticThisInstruction+numDynamicWritableThisInstruction:numStaticThisInstruction+numDynamicWritableThisInstruction+numDynamicReadonlyThisInstruction])
return readonly
}(),
}
}
default:
return nil
}
}(),
},
StackHeight: nil,
}

parsedInstructionJSON, err := instrParams.ParseInstruction()
if err != nil || parsedInstructionJSON == nil || !strings.HasPrefix(strings.TrimSpace(string(parsedInstructionJSON)), "{") {
nonParseadInstructionJSON := map[string]any{
"accounts": func() []string {
out := make([]string, len(inst.Accounts))
for i, v := range inst.Accounts {
out[i] = tx.Message.AccountKeys[v].String()
}
return out
}(),
"data": base58.Encode(inst.Data),
"programId": programId.String(),
"stackHeight": nil,
}
}
asRaw, _ := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(nonParseadInstructionJSON)
parsedInstructions = append(parsedInstructions, asRaw)
} else {
parsedInstructions = append(parsedInstructions, parsedInstructionJSON)
return resp, metaJSON, nil
}
}

resp, err := txstatus.FromTransaction(tx)
if err != nil {
return nil, fmt.Errorf("failed to convert transaction to txstatus.Transaction: %w", err)
}
resp.Message.Instructions = parsedInstructions

return resp, nil
return resp, meta, nil
case solana.EncodingJSON:
return tx, nil
return tx, meta, nil
default:
return nil, fmt.Errorf("unsupported encoding")
return nil, nil, fmt.Errorf("unsupported encoding")
}
}

func clone[T any](in []T) []T {
out := make([]T, len(in))
copy(out, in)
return out
}

func byeSliceToUint16Slice(in []byte) []uint16 {
out := make([]uint16, len(in))
for i, v := range in {
out[i] = uint16(v)
}
return out
}

func byteSlicesToKeySlices(keys [][]byte) []solana.PublicKey {
var out []solana.PublicKey
for _, key := range keys {
Expand Down

0 comments on commit 74ef21c

Please sign in to comment.