Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions address_table_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package solana

import orderedmap "github.com/wk8/go-ordered-map/v2"

func newAddressTableMap(capacity int) *orderedmap.OrderedMap[PublicKey, PublicKeySlice] {
return orderedmap.New[PublicKey, PublicKeySlice](orderedmap.WithCapacity[PublicKey, PublicKeySlice](capacity))
}

func addressTableMapFromMap(tables map[PublicKey]PublicKeySlice) *orderedmap.OrderedMap[PublicKey, PublicKeySlice] {
om := newAddressTableMap(len(tables))
for k, v := range tables {
om.Set(k, v)
}
return om
}

func addressTableMapFromSlice(tables []AddressTableEntry) *orderedmap.OrderedMap[PublicKey, PublicKeySlice] {
om := newAddressTableMap(len(tables))
for _, entry := range tables {
om.Set(entry.TableKey, entry.Addresses)
}
return om
}

func addressTableMapToMap(om *orderedmap.OrderedMap[PublicKey, PublicKeySlice]) map[PublicKey]PublicKeySlice {
out := make(map[PublicKey]PublicKeySlice, om.Len())
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
out[pair.Key] = pair.Value
}
return out
}

func addressTableMapToSlice(om *orderedmap.OrderedMap[PublicKey, PublicKeySlice]) []AddressTableEntry {
out := make([]AddressTableEntry, 0, om.Len())
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
out = append(out, AddressTableEntry{TableKey: pair.Key, Addresses: pair.Value})
}
return out
}
115 changes: 115 additions & 0 deletions address_table_map_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package solana

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
testTableKey1 = MustPublicKeyFromBase58("8Vaso6eE1pWktDHwy2qQBB1fhjmBgwzhoXQKe1sxtFjn")
testTableKey2 = MustPublicKeyFromBase58("FqtwFavD9v99FvoaZrY14bGatCQa9ChsMVphEUNAWHeG")
testAddr1 = MustPublicKeyFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
testAddr2 = MustPublicKeyFromBase58("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4")
testAddr3 = MustPublicKeyFromBase58("So11111111111111111111111111111111111111112")
)

func TestAddressTableMapFromMap_RoundTrip(t *testing.T) {
tables := map[PublicKey]PublicKeySlice{
testTableKey1: {testAddr1, testAddr2},
testTableKey2: {testAddr3},
}

om := addressTableMapFromMap(tables)
require.Equal(t, 2, om.Len())

result := addressTableMapToMap(om)
assert.Equal(t, tables, result)
}

func TestAddressTableMapFromMap_Empty(t *testing.T) {
om := addressTableMapFromMap(map[PublicKey]PublicKeySlice{})
assert.Equal(t, 0, om.Len())
assert.Equal(t, map[PublicKey]PublicKeySlice{}, addressTableMapToMap(om))
}

func TestAddressTableMapFromSlice_PreservesOrder(t *testing.T) {
entries := []AddressTableEntry{
{TableKey: testTableKey1, Addresses: PublicKeySlice{testAddr1, testAddr2}},
{TableKey: testTableKey2, Addresses: PublicKeySlice{testAddr3}},
}

om := addressTableMapFromSlice(entries)
require.Equal(t, 2, om.Len())

// Verify insertion order is preserved.
result := addressTableMapToSlice(om)
require.Len(t, result, 2)
assert.Equal(t, testTableKey1, result[0].TableKey)
assert.Equal(t, PublicKeySlice{testAddr1, testAddr2}, result[0].Addresses)
assert.Equal(t, testTableKey2, result[1].TableKey)
assert.Equal(t, PublicKeySlice{testAddr3}, result[1].Addresses)
}

func TestAddressTableMapFromSlice_Empty(t *testing.T) {
om := addressTableMapFromSlice([]AddressTableEntry{})
assert.Equal(t, 0, om.Len())
assert.Equal(t, []AddressTableEntry{}, addressTableMapToSlice(om))
}

func TestAddressTableMapToSlice_RoundTrip(t *testing.T) {
entries := []AddressTableEntry{
{TableKey: testTableKey2, Addresses: PublicKeySlice{testAddr3}},
{TableKey: testTableKey1, Addresses: PublicKeySlice{testAddr1, testAddr2}},
}

result := addressTableMapToSlice(addressTableMapFromSlice(entries))
assert.Equal(t, entries, result)
}

func TestAddressTableMapToMap_NilSafe(t *testing.T) {
// nil ordered map should return empty plain map without panicking.
assert.NotPanics(t, func() {
result := addressTableMapToMap(nil)
assert.Equal(t, map[PublicKey]PublicKeySlice{}, result)
})
}

func TestAddressTableMapToSlice_NilSafe(t *testing.T) {
assert.NotPanics(t, func() {
result := addressTableMapToSlice(nil)
assert.Equal(t, []AddressTableEntry{}, result)
})
}

// TestAddressTableMapFromSlice_OrderDeterminesTablePriority verifies that when
// the same address appears in two tables, the first entry in the slice wins.
func TestAddressTableMapFromSlice_OrderDeterminesTablePriority(t *testing.T) {
sharedAddr := testAddr1

entries := []AddressTableEntry{
{TableKey: testTableKey1, Addresses: PublicKeySlice{sharedAddr, testAddr2}},
{TableKey: testTableKey2, Addresses: PublicKeySlice{sharedAddr, testAddr3}},
}

om := addressTableMapFromSlice(entries)

// Build the same addressLookupKeysMap that NewTransaction builds to confirm
// table1 wins for the shared address.
addressLookupKeysMap := make(map[PublicKey]addressTablePubkeyWithIndex)
for pair := om.Oldest(); pair != nil; pair = pair.Next() {
for i, addr := range pair.Value {
if _, exists := addressLookupKeysMap[addr]; !exists {
addressLookupKeysMap[addr] = addressTablePubkeyWithIndex{
addressTable: pair.Key,
index: uint8(i),
}
}
}
}

entry := addressLookupKeysMap[sharedAddr]
assert.Equal(t, testTableKey1, entry.addressTable, "first slice entry should win for shared address")
assert.Equal(t, uint8(0), entry.index)
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
require (
cloud.google.com/go v0.56.0 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/blendle/zapdriver v1.3.1 // indirect
github.com/daaku/go.zipexe v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.4.7 // indirect
Expand All @@ -21,6 +22,7 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.11 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
Expand All @@ -33,6 +35,7 @@ require (
github.com/spf13/cast v1.3.0 // indirect
github.com/spf13/jwalterweatherman v1.0.0 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
Expand Down Expand Up @@ -70,7 +73,7 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.8.1
go.mongodb.org/mongo-driver v1.12.2
go.opencensus.io v0.22.5 // indirect
go.uber.org/ratelimit v0.2.0
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgp
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
Expand Down Expand Up @@ -178,6 +180,7 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
Expand All @@ -203,6 +206,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
Expand Down Expand Up @@ -299,19 +304,27 @@ github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyi
github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
Expand Down
37 changes: 32 additions & 5 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

bin "github.com/gagliardetto/binary"
"github.com/gagliardetto/treeout"
orderedmap "github.com/wk8/go-ordered-map/v2"

"github.com/gagliardetto/solana-go/text"
)
Expand Down Expand Up @@ -110,7 +111,7 @@ type Message struct {
// The actual tables that contain the list of account pubkeys.
// NOTE: you need to fetch these from the chain, and then call `SetAddressTables`
// before you use this transaction -- otherwise, you will get a panic.
addressTables map[PublicKey]PublicKeySlice
addressTables *orderedmap.OrderedMap[PublicKey, PublicKeySlice]

resolved bool // if true, the lookups have been resolved, and the `AccountKeys` slice contains all the accounts (static + dynamic).
}
Expand All @@ -119,6 +120,26 @@ type Message struct {
// Use `mx.GetAddressTableLookups().GetTableIDs()` to get the list of all address table IDs.
// NOTE: you can call this once.
func (mx *Message) SetAddressTables(tables map[PublicKey]PublicKeySlice) error {
if mx.addressTables != nil {
return fmt.Errorf("address tables already set")
}
mx.addressTables = addressTableMapFromMap(tables)
return nil
}

// SetAddressTablesSlice sets the actual address tables from a slice, preserving the
// provided order. Use this when insertion order matters, e.g. to control which table
// takes priority when an address appears in multiple tables.
// NOTE: you can call this once.
func (mx *Message) SetAddressTablesSlice(tables []AddressTableEntry) error {
if mx.addressTables != nil {
return fmt.Errorf("address tables already set")
}
mx.addressTables = addressTableMapFromSlice(tables)
return nil
}

func (mx *Message) setAddressTablesOrdered(tables *orderedmap.OrderedMap[PublicKey, PublicKeySlice]) error {
if mx.addressTables != nil {
return fmt.Errorf("address tables already set")
}
Expand All @@ -127,9 +148,15 @@ func (mx *Message) SetAddressTables(tables map[PublicKey]PublicKeySlice) error {
}

// GetAddressTables returns the actual address tables used by this message.
// NOTE: you must have called `SetAddressTable` before being able to use this method.
// NOTE: you must have called `SetAddressTables` before being able to use this method.
func (mx *Message) GetAddressTables() map[PublicKey]PublicKeySlice {
return mx.addressTables
return addressTableMapToMap(mx.addressTables)
}

// GetAddressTablesSlice returns the address tables as a slice in their insertion order.
// NOTE: you must have called `SetAddressTables` before being able to use this method.
func (mx *Message) GetAddressTablesSlice() []AddressTableEntry {
return addressTableMapToSlice(mx.addressTables)
}

var _ bin.EncoderDecoder = &Message{}
Expand Down Expand Up @@ -421,7 +448,7 @@ func (mx Message) GetAddressTableLookupAccounts() (PublicKeySlice, error) {
var readonly PublicKeySlice

for _, lookup := range mx.AddressTableLookups {
table, ok := mx.addressTables[lookup.AccountKey]
table, ok := mx.addressTables.Get(lookup.AccountKey)
if !ok {
return writable, fmt.Errorf("address table lookup not found for account: %s", lookup.AccountKey)
}
Expand Down Expand Up @@ -657,7 +684,7 @@ func (m Message) checkPreconditions() error {
// and there are > 0 lookups,
// but the address table is empty,
// then we can't build the account meta list:
if m.IsVersioned() && m.AddressTableLookups.NumLookups() > 0 && (m.addressTables == nil || len(m.addressTables) == 0) {
if m.IsVersioned() && m.AddressTableLookups.NumLookups() > 0 && m.addressTables.Len() == 0 {
return fmt.Errorf("cannot build account meta list without address tables")
}

Expand Down
Loading