@@ -20,17 +20,32 @@ const (
2020type Signature [ed25519 .SignatureSize ]byte
2121type Blockhash [sha256 .Size ]byte
2222
23+ type MessageVersion uint8
24+
25+ const (
26+ MessageVersionLegacy MessageVersion = iota
27+ MessageVersion0
28+ )
29+
2330type Header struct {
2431 NumSignatures byte
2532 NumReadonlySigned byte
2633 NumReadOnly byte
2734}
2835
36+ type MessageAddressTableLookup struct {
37+ PublicKey ed25519.PublicKey
38+ WritableIndexes []byte
39+ ReadonlyIndexes []byte
40+ }
41+
2942type Message struct {
30- Header Header
31- Accounts []ed25519.PublicKey
32- RecentBlockhash Blockhash
33- Instructions []CompiledInstruction
43+ version MessageVersion
44+ Header Header
45+ Accounts []ed25519.PublicKey
46+ RecentBlockhash Blockhash
47+ Instructions []CompiledInstruction
48+ AddressTableLookups []MessageAddressTableLookup
3449}
3550
3651type Transaction struct {
@@ -39,6 +54,15 @@ type Transaction struct {
3954}
4055
4156func NewTransaction (payer ed25519.PublicKey , instructions ... Instruction ) Transaction {
57+ return newTransaction (payer , nil , instructions )
58+ }
59+
60+ func NewVersionedTransaction (payer ed25519.PublicKey , addressLookupTables []AddressLookupTable , instructions []Instruction ) Transaction {
61+ return newTransaction (payer , addressLookupTables , instructions )
62+ }
63+
64+ // todo: consolidate to new constructor
65+ func newTransaction (payer ed25519.PublicKey , addressLookupTables []AddressLookupTable , instructions []Instruction ) Transaction {
4266 accounts := []AccountMeta {
4367 {
4468 PublicKey : payer ,
@@ -65,8 +89,45 @@ func NewTransaction(payer ed25519.PublicKey, instructions ...Instruction) Transa
6589 accounts = filterUnique (accounts )
6690 sort .Sort (SortableAccountMeta (accounts ))
6791
92+ // Sort address tables to guarantee consistent marshalling
93+ sortedAddressLookupTables := make ([]AddressLookupTable , len (addressLookupTables ))
94+ copy (sortedAddressLookupTables , addressLookupTables )
95+ sort .Sort (SortableAddressLookupTables (sortedAddressLookupTables ))
96+
97+ writableAddressTableIndexes := make ([][]byte , len (sortedAddressLookupTables ))
98+ readonlyAddressTableIndexes := make ([][]byte , len (sortedAddressLookupTables ))
99+
68100 var m Message
69101 for _ , account := range accounts {
102+ // If the account is eligible for dynamic loading, then pull its index
103+ // from the first address table where it's defined.
104+ var isDynamicallyLoaded bool
105+ if ! account .isPayer && ! account .IsSigner && ! account .isProgram {
106+ for i , addressLookupTable := range sortedAddressLookupTables {
107+ for j , address := range addressLookupTable .Addresses {
108+ if bytes .Equal (address , account .PublicKey ) {
109+ isDynamicallyLoaded = true
110+
111+ if account .IsWritable {
112+ writableAddressTableIndexes [i ] = append (writableAddressTableIndexes [i ], byte (j ))
113+ } else {
114+ readonlyAddressTableIndexes [i ] = append (readonlyAddressTableIndexes [i ], byte (j ))
115+ }
116+
117+ break
118+ }
119+ }
120+
121+ if isDynamicallyLoaded {
122+ break
123+ }
124+ }
125+ }
126+ if isDynamicallyLoaded {
127+ continue
128+ }
129+
130+ // Otherwise, the account is defined statically
70131 m .Accounts = append (m .Accounts , account .PublicKey )
71132
72133 if account .IsSigner {
@@ -80,21 +141,58 @@ func NewTransaction(payer ed25519.PublicKey, instructions ...Instruction) Transa
80141 }
81142 }
82143
144+ // Consolidate static and dynamically loaded accounts into an ordered list,
145+ // which is used for index references encoded in the message
146+ dynamicWritableAccounts := make ([]ed25519.PublicKey , 0 )
147+ dynamicReadonlyAccount := make ([]ed25519.PublicKey , 0 )
148+ for i , writableAddressTableIndexes := range writableAddressTableIndexes {
149+ for _ , index := range writableAddressTableIndexes {
150+ writableAccount := sortedAddressLookupTables [i ].Addresses [index ]
151+ dynamicWritableAccounts = append (dynamicWritableAccounts , writableAccount )
152+ }
153+ }
154+ for i , readonlyAddressTableIndexes := range readonlyAddressTableIndexes {
155+ for _ , index := range readonlyAddressTableIndexes {
156+ readonlyAccount := sortedAddressLookupTables [i ].Addresses [index ]
157+ dynamicReadonlyAccount = append (dynamicReadonlyAccount , readonlyAccount )
158+ }
159+ }
160+ var allAccounts []ed25519.PublicKey
161+ allAccounts = append (allAccounts , m .Accounts ... )
162+ allAccounts = append (allAccounts , dynamicWritableAccounts ... )
163+ allAccounts = append (allAccounts , dynamicReadonlyAccount ... )
164+
83165 // Generate the compiled instruction, which uses indices instead
84166 // of raw account keys.
85167 for _ , i := range instructions {
86168 c := CompiledInstruction {
87- ProgramIndex : byte (indexOf (m . Accounts , i .Program )),
169+ ProgramIndex : byte (indexOf (allAccounts , i .Program )),
88170 Data : i .Data ,
89171 }
90172
91173 for _ , a := range i .Accounts {
92- c .Accounts = append (c .Accounts , byte (indexOf (m . Accounts , a .PublicKey )))
174+ c .Accounts = append (c .Accounts , byte (indexOf (allAccounts , a .PublicKey )))
93175 }
94176
95177 m .Instructions = append (m .Instructions , c )
96178 }
97179
180+ // Generate the compiled message address table lookups
181+ for i , addressLookupTable := range sortedAddressLookupTables {
182+ if len (writableAddressTableIndexes [i ]) == 0 && len (readonlyAddressTableIndexes [i ]) == 0 {
183+ continue
184+ }
185+
186+ m .AddressTableLookups = append (m .AddressTableLookups , MessageAddressTableLookup {
187+ PublicKey : addressLookupTable .PublicKey ,
188+ WritableIndexes : writableAddressTableIndexes [i ],
189+ ReadonlyIndexes : readonlyAddressTableIndexes [i ],
190+ })
191+ }
192+ if len (m .AddressTableLookups ) > 0 {
193+ m .version = MessageVersion0
194+ }
195+
98196 for i := range m .Accounts {
99197 if len (m .Accounts [i ]) == 0 {
100198 m .Accounts [i ] = make ([]byte , ed25519 .PublicKeySize )
@@ -118,11 +216,12 @@ func (t *Transaction) String() string {
118216 sb .WriteString (fmt .Sprintf (" %d: %s\n " , i , base58 .Encode (s [:])))
119217 }
120218 sb .WriteString ("Message:\n " )
219+ sb .WriteString (fmt .Sprintf (" Version: %s\n " , t .Message .version .String ()))
121220 sb .WriteString (" Header:\n " )
122221 sb .WriteString (fmt .Sprintf (" NumSignatures: %d\n " , t .Message .Header .NumSignatures ))
123222 sb .WriteString (fmt .Sprintf (" NumReadOnly: %d\n " , t .Message .Header .NumReadOnly ))
124223 sb .WriteString (fmt .Sprintf (" NumReadOnlySigned: %d\n " , t .Message .Header .NumReadonlySigned ))
125- sb .WriteString (" Accounts:\n " )
224+ sb .WriteString (" Static Accounts:\n " )
126225 for i , a := range t .Message .Accounts {
127226 sb .WriteString (fmt .Sprintf (" %d: %s\n " , i , base58 .Encode (a )))
128227 }
@@ -133,7 +232,14 @@ func (t *Transaction) String() string {
133232 sb .WriteString (fmt .Sprintf (" Accounts: %v\n " , t .Message .Instructions [i ].Accounts ))
134233 sb .WriteString (fmt .Sprintf (" Data: %v\n " , t .Message .Instructions [i ].Data ))
135234 }
136-
235+ if len (t .Message .AddressTableLookups ) > 0 {
236+ sb .WriteString (" Address Table Lookups:\n " )
237+ for i := range t .Message .AddressTableLookups {
238+ sb .WriteString (fmt .Sprintf (" %s:\n " , base58 .Encode (t .Message .AddressTableLookups [i ].PublicKey )))
239+ sb .WriteString (fmt .Sprintf (" Writable Indexes: %v\n " , t .Message .AddressTableLookups [i ].WritableIndexes ))
240+ sb .WriteString (fmt .Sprintf (" Readonly Indexes: %v\n " , t .Message .AddressTableLookups [i ].ReadonlyIndexes ))
241+ }
242+ }
137243 return sb .String ()
138244}
139245
@@ -198,3 +304,13 @@ func indexOf(slice []ed25519.PublicKey, item ed25519.PublicKey) int {
198304
199305 return - 1
200306}
307+
308+ func (v MessageVersion ) String () string {
309+ switch v {
310+ case MessageVersionLegacy :
311+ return "legacy"
312+ case MessageVersion0 :
313+ return "v0"
314+ }
315+ return "unknown"
316+ }
0 commit comments