cache address table
This commit is contained in:
1
go.mod
1
go.mod
@@ -22,6 +22,7 @@ require (
|
|||||||
github.com/gagliardetto/binary v0.8.0 // indirect
|
github.com/gagliardetto/binary v0.8.0 // indirect
|
||||||
github.com/gagliardetto/treeout v0.1.4 // indirect
|
github.com/gagliardetto/treeout v0.1.4 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.13.6 // indirect
|
github.com/klauspost/compress v1.13.6 // indirect
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -36,6 +36,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ import (
|
|||||||
|
|
||||||
"github.com/gagliardetto/solana-go"
|
"github.com/gagliardetto/solana-go"
|
||||||
"github.com/gagliardetto/solana-go/rpc"
|
"github.com/gagliardetto/solana-go/rpc"
|
||||||
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/panjf2000/ants/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AddressTables struct {
|
type AddressTables struct {
|
||||||
rpcClient *rpc.Client
|
rpcClient *rpc.Client
|
||||||
mux sync.RWMutex
|
mux sync.RWMutex
|
||||||
tables map[solana.PublicKey][]solana.PublicKey
|
tables *lru.Cache[solana.PublicKey, []solana.PublicKey]
|
||||||
loading map[solana.PublicKey]struct{}
|
loading map[solana.PublicKey]struct{}
|
||||||
|
|
||||||
pool *ants.Pool
|
pool *ants.Pool
|
||||||
@@ -21,9 +22,10 @@ type AddressTables struct {
|
|||||||
|
|
||||||
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
|
func NewAddressTables(rpcClient *rpc.Client) *AddressTables {
|
||||||
pool, _ := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
pool, _ := ants.NewPool(10, ants.WithPreAlloc(true), ants.WithNonblocking(true))
|
||||||
|
cache, _ := lru.New[solana.PublicKey, []solana.PublicKey](10000)
|
||||||
return &AddressTables{
|
return &AddressTables{
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
tables: make(map[solana.PublicKey][]solana.PublicKey),
|
tables: cache,
|
||||||
loading: make(map[solana.PublicKey]struct{}),
|
loading: make(map[solana.PublicKey]struct{}),
|
||||||
pool: pool,
|
pool: pool,
|
||||||
}
|
}
|
||||||
@@ -53,9 +55,8 @@ func (at *AddressTables) loadAddressTable(tablePubkey solana.PublicKey) ([]solan
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey {
|
func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uint8) []solana.PublicKey {
|
||||||
|
|
||||||
at.mux.RLock()
|
at.mux.RLock()
|
||||||
addresses, ok := at.tables[tablePubkey]
|
addresses, ok := at.tables.Get(tablePubkey)
|
||||||
if !ok {
|
if !ok {
|
||||||
at.mux.RUnlock()
|
at.mux.RUnlock()
|
||||||
_ = at.pool.Submit(func() {
|
_ = at.pool.Submit(func() {
|
||||||
@@ -79,8 +80,8 @@ func (at *AddressTables) GetAddressTable(tablePubkey solana.PublicKey, idx []uin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
at.mux.Lock()
|
at.mux.Lock()
|
||||||
at.tables[tablePubkey] = table
|
at.tables.Add(tablePubkey, table)
|
||||||
total := len(at.tables)
|
total := at.tables.Len()
|
||||||
delete(at.loading, tablePubkey)
|
delete(at.loading, tablePubkey)
|
||||||
at.mux.Unlock()
|
at.mux.Unlock()
|
||||||
logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total)
|
logger.Info("loadAddressTable", "table", tablePubkey.String(), "table count:", total)
|
||||||
|
|||||||
@@ -80,11 +80,6 @@ var (
|
|||||||
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
|
terminalAmmSellTokensIX = []byte{0x40, 0x64, 0x97, 0xb9, 0x16, 0xfa, 0xec, 0xb1}
|
||||||
)
|
)
|
||||||
|
|
||||||
// table lookups
|
|
||||||
const (
|
|
||||||
photonTableLookup = "3r6paeFSLpeUVmWtShb5uZtXYpcBE3729kUxkUS7xKi1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type compiledInstruction struct {
|
type compiledInstruction struct {
|
||||||
ProgramIDIndex uint8
|
ProgramIDIndex uint8
|
||||||
Accounts []uint8
|
Accounts []uint8
|
||||||
@@ -406,7 +401,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 8 {
|
if len(instruction.Data) < 8 {
|
||||||
return nil, fmt.Errorf("data too short for create v2 args")
|
return nil, fmt.Errorf("data too short for pump create v2 args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -443,7 +438,7 @@ func parsePumpCreateV2(tx *versionedTransaction, instruction *compiledInstructio
|
|||||||
|
|
||||||
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
|
func decodePumpBuyArgs(data []byte) (uint64, uint64, error) {
|
||||||
if len(data) < 9 {
|
if len(data) < 9 {
|
||||||
return 0, 0, fmt.Errorf("data too short for buy args")
|
return 0, 0, fmt.Errorf("data too short for pump buy buy args, len=%d", len(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
var args pumpBuyArgs
|
var args pumpBuyArgs
|
||||||
@@ -507,7 +502,7 @@ func parsePumpBuy(tx *versionedTransaction, instruction *compiledInstruction) (*
|
|||||||
|
|
||||||
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
|
func decodePumpSellArgs(data []byte) (uint64, uint64, error) {
|
||||||
if len(data) < 9 {
|
if len(data) < 9 {
|
||||||
return 0, 0, fmt.Errorf("data too short for sell args")
|
return 0, 0, fmt.Errorf("data too short for pump sell sell args, len=%d", len(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
var args pumpExtendedSellArgs
|
var args pumpExtendedSellArgs
|
||||||
@@ -597,7 +592,7 @@ func parseAzczAmmBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(instruction.Data) < 17 {
|
if len(instruction.Data) < 17 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for azcz amm buy args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9])
|
solAmount := binary.LittleEndian.Uint64(instruction.Data[1:9])
|
||||||
@@ -637,7 +632,7 @@ func parseAzczBuy(tx *versionedTransaction, instructionIndex int) (*TxSignal, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(instruction.Data) < 2 {
|
if len(instruction.Data) < 2 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for azcz buy args len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
var args azczBuyArgs
|
var args azczBuyArgs
|
||||||
@@ -691,7 +686,7 @@ func parseF5tfInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(instruction.Data) < 2 {
|
if len(instruction.Data) < 2 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for f5tf buy args len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
var args f5tfBuyArgs
|
var args f5tfBuyArgs
|
||||||
@@ -726,8 +721,11 @@ func parseFlasInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
|||||||
if len(instruction.Data) == 0 {
|
if len(instruction.Data) == 0 {
|
||||||
return nil, fmt.Errorf("data is empty")
|
return nil, fmt.Errorf("data is empty")
|
||||||
}
|
}
|
||||||
|
if len(instruction.Data) == 10 && instruction.Data[0] == 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
if len(instruction.Data) < 20 {
|
if len(instruction.Data) < 20 {
|
||||||
return nil, fmt.Errorf("data too short for args")
|
return nil, fmt.Errorf("data too short for args flas instruction, len: %d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
methodData := instruction.Data[17:20]
|
methodData := instruction.Data[17:20]
|
||||||
if !matchMethod(methodData, flasBuyTokensIX) {
|
if !matchMethod(methodData, flasBuyTokensIX) {
|
||||||
@@ -929,7 +927,7 @@ func parsePhotonBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 16 {
|
if len(instruction.Data) < 16 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for photon buy args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -970,7 +968,7 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 16 {
|
if len(instruction.Data) < 16 {
|
||||||
return nil, fmt.Errorf("data too short for swap args")
|
return nil, fmt.Errorf("data too short for swap args for photon. len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -979,12 +977,11 @@ func parsePhotonSwap(tx *versionedTransaction, instruction *compiledInstruction)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteIndex := int(instruction.Accounts[4])
|
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||||
quote, err := resolveQuoteAccount(tx, quoteIndex, []string{photonTableLookup}, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if quote != wsolMint {
|
if !quote.Equals(solana.WrappedSol) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,7 +1170,7 @@ func parseTermSell(tx *versionedTransaction, instruction *compiledInstruction) (
|
|||||||
|
|
||||||
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
|
func decodePumpAmmBuyArgs(data []byte) (uint64, uint64, error) {
|
||||||
if len(data) < 9 {
|
if len(data) < 9 {
|
||||||
return 0, 0, fmt.Errorf("data too short for buy args")
|
return 0, 0, fmt.Errorf("data too short for pump amm buy args, len=%d", len(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
var args pumpAmmBuyArgs
|
var args pumpAmmBuyArgs
|
||||||
@@ -1212,12 +1209,11 @@ func parsePumpAmmBuy(tx *versionedTransaction, instruction *compiledInstruction)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
quoteIndex := int(instruction.Accounts[4])
|
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||||
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if quote != wsolMint {
|
if !quote.Equals(solana.WrappedSol) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,12 +1255,11 @@ func parsePumpAmmSell(tx *versionedTransaction, instruction *compiledInstruction
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
quoteIndex := int(instruction.Accounts[4])
|
quote, err := getStaticKey(staticKeys, int(instruction.Accounts[4]))
|
||||||
quote, err := resolveQuoteAccount(tx, quoteIndex, nil, 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if quote != wsolMint {
|
if !quote.Equals(solana.WrappedSol) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1308,7 +1303,7 @@ func parseBoboInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 16 {
|
if len(instruction.Data) < 16 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for bobo buy args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -1371,7 +1366,7 @@ func parseQtkvSell(tx *versionedTransaction, instructionIndex int) (*TxSignal, e
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 24 {
|
if len(instruction.Data) < 24 {
|
||||||
return nil, fmt.Errorf("data too short for sell args")
|
return nil, fmt.Errorf("data too short for qtkv sell args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -1409,7 +1404,7 @@ func parseQtkvAmmSell(tx *versionedTransaction, instructionIndex int) (*TxSignal
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 24 {
|
if len(instruction.Data) < 24 {
|
||||||
return nil, fmt.Errorf("data too short for sell args")
|
return nil, fmt.Errorf("data too short for qtkv amm sell args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -1497,7 +1492,7 @@ func parseFjszInstruction(tx *versionedTransaction, instructionIndex int) (*TxSi
|
|||||||
return nil, fmt.Errorf("accounts too short")
|
return nil, fmt.Errorf("accounts too short")
|
||||||
}
|
}
|
||||||
if len(instruction.Data) < 16 {
|
if len(instruction.Data) < 16 {
|
||||||
return nil, fmt.Errorf("data too short for buy args")
|
return nil, fmt.Errorf("data too short for fjzs buy args, len=%d", len(instruction.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
staticKeys := tx.Message.StaticAccountKeys
|
||||||
@@ -1553,43 +1548,6 @@ func parseTerminalInstruction(tx *versionedTransaction, instructionIndex int) (*
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveQuoteAccount(tx *versionedTransaction, quoteIndex int, expectedTableKeys []string, targetIndex uint8) (string, error) {
|
|
||||||
staticKeys := tx.Message.StaticAccountKeys
|
|
||||||
if quoteIndex < len(staticKeys) {
|
|
||||||
quoteKey := staticKeys[quoteIndex].String()
|
|
||||||
return quoteKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// attempt to load from address table lookup
|
|
||||||
if len(expectedTableKeys) == 0 || len(tx.Message.AddressTableLookups) != 1 {
|
|
||||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
table := tx.Message.AddressTableLookups[0]
|
|
||||||
match := false
|
|
||||||
for _, key := range expectedTableKeys {
|
|
||||||
if table.AccountKey.String() == key {
|
|
||||||
match = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
indexOfTarget := indexOf(table.ReadonlyIndexes, targetIndex)
|
|
||||||
if indexOfTarget < 0 {
|
|
||||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedIndex := len(staticKeys) + len(table.WritableIndexes) + indexOfTarget
|
|
||||||
if quoteIndex != expectedIndex {
|
|
||||||
return "", fmt.Errorf("parse quote from table lookup failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
return wsolMint, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexOf(haystack []uint8, needle uint8) int {
|
func indexOf(haystack []uint8, needle uint8) int {
|
||||||
for i, v := range haystack {
|
for i, v := range haystack {
|
||||||
if v == needle {
|
if v == needle {
|
||||||
|
|||||||
Reference in New Issue
Block a user