solana-go/message.go

287 lines
8.1 KiB
Go

// Copyright 2021 github.com/gagliardetto
// This file has been modified by github.com/gagliardetto
//
// Copyright 2020 dfuse Platform Inc.
//
// 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 solana
import (
"encoding/base64"
"fmt"
bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/solana-go/text"
"github.com/gagliardetto/treeout"
)
type Message struct {
// List of base-58 encoded public keys used by the transaction,
// including by the instructions and for signatures.
// The first `message.header.numRequiredSignatures` public keys must sign the transaction.
AccountKeys []PublicKey `json:"accountKeys"`
// Details the account types and signatures required by the transaction.
Header MessageHeader `json:"header"`
// A base-58 encoded hash of a recent block in the ledger used to
// prevent transaction duplication and to give transactions lifetimes.
RecentBlockhash Hash `json:"recentBlockhash"`
// List of program instructions that will be executed in sequence
// and committed in one atomic transaction if all succeed.
Instructions []CompiledInstruction `json:"instructions"`
}
var _ bin.EncoderDecoder = &Message{}
func (mx *Message) EncodeToTree(txTree treeout.Branches) {
txTree.Child(text.Sf("RecentBlockhash: %s", mx.RecentBlockhash))
txTree.Child(fmt.Sprintf("AccountKeys[len=%v]", len(mx.AccountKeys))).ParentFunc(func(accountKeysBranch treeout.Branches) {
for _, key := range mx.AccountKeys {
accountKeysBranch.Child(text.ColorizeBG(key.String()))
}
})
txTree.Child("Header").ParentFunc(func(message treeout.Branches) {
mx.Header.EncodeToTree(message)
})
}
func (header *MessageHeader) EncodeToTree(mxBranch treeout.Branches) {
mxBranch.Child(text.Sf("NumRequiredSignatures: %v", header.NumRequiredSignatures))
mxBranch.Child(text.Sf("NumReadonlySignedAccounts: %v", header.NumReadonlySignedAccounts))
mxBranch.Child(text.Sf("NumReadonlyUnsignedAccounts: %v", header.NumReadonlyUnsignedAccounts))
}
func (mx *Message) MarshalBinary() ([]byte, error) {
buf := []byte{
mx.Header.NumRequiredSignatures,
mx.Header.NumReadonlySignedAccounts,
mx.Header.NumReadonlyUnsignedAccounts,
}
bin.EncodeCompactU16Length(&buf, len(mx.AccountKeys))
for _, key := range mx.AccountKeys {
buf = append(buf, key[:]...)
}
buf = append(buf, mx.RecentBlockhash[:]...)
bin.EncodeCompactU16Length(&buf, len(mx.Instructions))
for _, instruction := range mx.Instructions {
buf = append(buf, byte(instruction.ProgramIDIndex))
bin.EncodeCompactU16Length(&buf, len(instruction.Accounts))
for _, accountIdx := range instruction.Accounts {
buf = append(buf, byte(accountIdx))
}
bin.EncodeCompactU16Length(&buf, len(instruction.Data))
buf = append(buf, instruction.Data...)
}
return buf, nil
}
func (mx Message) MarshalWithEncoder(encoder *bin.Encoder) error {
out, err := mx.MarshalBinary()
if err != nil {
return err
}
return encoder.WriteBytes(out, false)
}
func (mx Message) ToBase64() string {
out, _ := mx.MarshalBinary()
return base64.StdEncoding.EncodeToString(out)
}
func (mx *Message) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
{
mx.Header.NumRequiredSignatures, err = decoder.ReadUint8()
if err != nil {
return err
}
mx.Header.NumReadonlySignedAccounts, err = decoder.ReadUint8()
if err != nil {
return err
}
mx.Header.NumReadonlyUnsignedAccounts, err = decoder.ReadUint8()
if err != nil {
return err
}
}
{
numAccountKeys, err := bin.DecodeCompactU16LengthFromByteReader(decoder)
if err != nil {
return err
}
for i := 0; i < numAccountKeys; i++ {
pubkeyBytes, err := decoder.ReadNBytes(32)
if err != nil {
return err
}
var sig PublicKey
copy(sig[:], pubkeyBytes)
mx.AccountKeys = append(mx.AccountKeys, sig)
}
}
{
recentBlockhashBytes, err := decoder.ReadNBytes(32)
if err != nil {
return err
}
var recentBlockhash Hash
copy(recentBlockhash[:], recentBlockhashBytes)
mx.RecentBlockhash = recentBlockhash
}
{
numInstructions, err := bin.DecodeCompactU16LengthFromByteReader(decoder)
if err != nil {
return err
}
for i := 0; i < numInstructions; i++ {
programIDIndex, err := decoder.ReadUint8()
if err != nil {
return err
}
var compInst CompiledInstruction
compInst.ProgramIDIndex = uint16(programIDIndex)
{
numAccounts, err := bin.DecodeCompactU16LengthFromByteReader(decoder)
if err != nil {
return err
}
for i := 0; i < numAccounts; i++ {
accountIndex, err := decoder.ReadUint8()
if err != nil {
return err
}
compInst.Accounts = append(compInst.Accounts, uint16(accountIndex))
}
}
{
dataLen, err := bin.DecodeCompactU16LengthFromByteReader(decoder)
if err != nil {
return err
}
dataBytes, err := decoder.ReadNBytes(dataLen)
if err != nil {
return err
}
compInst.Data = Base58(dataBytes)
}
mx.Instructions = append(mx.Instructions, compInst)
}
}
return nil
}
func (m *Message) AccountMetaList() AccountMetaSlice {
out := make(AccountMetaSlice, len(m.AccountKeys))
for i, a := range m.AccountKeys {
out[i] = &AccountMeta{
PublicKey: a,
IsSigner: m.IsSigner(a),
IsWritable: m.IsWritable(a),
}
}
return out
}
// Signers returns the pubkeys of all accounts that are signers.
func (m *Message) Signers() PublicKeySlice {
out := make(PublicKeySlice, 0, len(m.AccountKeys))
for _, a := range m.AccountKeys {
if m.IsSigner(a) {
out = append(out, a)
}
}
return out
}
// Writable returns the pubkeys of all accounts that are writable.
func (m *Message) Writable() (out PublicKeySlice) {
for _, a := range m.AccountKeys {
if m.IsWritable(a) {
out = append(out, a)
}
}
return out
}
func (m *Message) ResolveProgramIDIndex(programIDIndex uint16) (PublicKey, error) {
if int(programIDIndex) < len(m.AccountKeys) {
return m.AccountKeys[programIDIndex], nil
}
return PublicKey{}, fmt.Errorf("programID index not found %d", programIDIndex)
}
func (m *Message) HasAccount(account PublicKey) bool {
for _, a := range m.AccountKeys {
if a.Equals(account) {
return true
}
}
return false
}
func (m *Message) IsSigner(account PublicKey) bool {
for idx, acc := range m.AccountKeys {
if acc.Equals(account) {
return idx < int(m.Header.NumRequiredSignatures)
}
}
return false
}
func (m *Message) IsWritable(account PublicKey) bool {
index := 0
found := false
for idx, acc := range m.AccountKeys {
if acc.Equals(account) {
found = true
index = idx
}
}
if !found {
return false
}
h := m.Header
return (index < int(h.NumRequiredSignatures-h.NumReadonlySignedAccounts)) ||
((index >= int(h.NumRequiredSignatures)) && (index < len(m.AccountKeys)-int(h.NumReadonlyUnsignedAccounts)))
}
func (m *Message) signerKeys() []PublicKey {
return m.AccountKeys[0:m.Header.NumRequiredSignatures]
}
type MessageHeader struct {
// The total number of signatures required to make the transaction valid.
// The signatures must match the first `numRequiredSignatures` of `message.account_keys`.
NumRequiredSignatures uint8 `json:"numRequiredSignatures"`
// The last numReadonlySignedAccounts of the signed keys are read-only accounts.
// Programs may process multiple transactions that load read-only accounts within
// a single PoH entry, but are not permitted to credit or debit lamports or modify
// account data.
// Transactions targeting the same read-write account are evaluated sequentially.
NumReadonlySignedAccounts uint8 `json:"numReadonlySignedAccounts"`
// The last `numReadonlyUnsignedAccounts` of the unsigned keys are read-only accounts.
NumReadonlyUnsignedAccounts uint8 `json:"numReadonlyUnsignedAccounts"`
}