Skip to content

Commit 0765f99

Browse files
authored
Merge branch 'main' into improve-account-behavior
2 parents 60b12a7 + ca456d4 commit 0765f99

File tree

9 files changed

+386
-13
lines changed

9 files changed

+386
-13
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ adheres to the format set out by [Keep a Changelog](https://keepachangelog.com/e
55

66
# Unreleased
77

8+
- [`Fix`] Ensure proper cleanup of response body on read error to prevent potential memory leak.
9+
810
# v1.5.0 (2/10/2024)
911

1012
- [`Fix`] Make NodeClient match AptosRpcClient interface

bcs/deserializer.go

+5
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,8 @@ func (des *Deserializer) setError(msg string, args ...any) {
318318
}
319319
des.err = fmt.Errorf(msg, args...)
320320
}
321+
322+
// DeserializeBytes deserializes a byte array prefixed with a length
323+
func (des *Deserializer) Serialized() *Serialized {
324+
return &Serialized{des.ReadBytes()}
325+
}

bcs/serialized.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package bcs
2+
3+
// Serialized represents a serialized transaction argument
4+
type Serialized struct {
5+
Value []byte
6+
}
7+
8+
// NewSerialized creates a new Serialized instance
9+
func NewSerialized(value []byte) *Serialized {
10+
return &Serialized{
11+
Value: value,
12+
}
13+
}
14+
15+
// Serialize serializes the Serialized instance
16+
func (s *Serialized) Serialized(serializer *Serializer) {
17+
serializer.WriteBytes(s.Value)
18+
}
19+
20+
// SerializeForEntryFunction serializes the Serialized instance for entry function
21+
func (s *Serialized) SerializedForEntryFunction(serializer *Serializer) {
22+
s.Serialized(serializer)
23+
}
24+
25+
// SerializeForScriptFunction serializes the Serialized instance for script function
26+
func (s *Serialized) SerializedForScriptFunction(serializer *Serializer) {
27+
serializer.Uleb128(uint32(9))
28+
s.Serialized(serializer)
29+
}

bcs/serializer.go

+13
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,16 @@ func SerializeOption[T any](ser *Serializer, input *T, serialize func(ser *Seria
352352
SerializeSequenceWithFunction([]T{*input}, ser, serialize)
353353
}
354354
}
355+
356+
// Serialized wraps already serialized bytes with BCS serialization
357+
// It prepends the byte array with its length (encoded as Uleb128) and writes the bytes
358+
// Primarily used for handling nested serialization structures where bytes are already in BCS format
359+
func (ser *Serializer) Serialized(s Serialized) {
360+
ser.WriteBytes(s.Value)
361+
}
362+
363+
func SerializeSerialized(input Serialized) ([]byte, error) {
364+
return SerializeSingle(func(ser *Serializer) {
365+
ser.Serialized(input)
366+
})
367+
}

client.go

+26
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,22 @@ type AptosRpcClient interface {
217217
// client.AccountTransactions(AccountOne, 1, 100) // Returns 100 transactions for 0x1
218218
AccountTransactions(address AccountAddress, start *uint64, limit *uint64) (data []*api.CommittedTransaction, err error)
219219

220+
// EventsByHandle retrieves events by event handle and field name for a given account.
221+
//
222+
// Arguments:
223+
// - account - The account address to get events for
224+
// - eventHandle - The event handle struct tag
225+
// - fieldName - The field in the event handle struct
226+
// - start - The starting sequence number. nil for most recent events
227+
// - limit - The number of events to return, 100 by default
228+
EventsByHandle(
229+
account AccountAddress,
230+
eventHandle string,
231+
fieldName string,
232+
start *uint64,
233+
limit *uint64,
234+
) ([]*api.Event, error)
235+
220236
// SubmitTransaction Submits an already signed transaction to the blockchain
221237
//
222238
// sender := NewEd25519Account()
@@ -649,6 +665,16 @@ func (client *Client) AccountTransactions(address AccountAddress, start *uint64,
649665
return client.nodeClient.AccountTransactions(address, start, limit)
650666
}
651667

668+
// EventsByHandle Get events by handle and field name for an account.
669+
// Start is a sequence number. Nil for most recent events.
670+
// Limit is a number of events to return, 100 by default.
671+
//
672+
// client.EventsByHandle(AccountOne, "0x2", "transfer", 0, 2) // Returns 2 events
673+
// client.EventsByHandle(AccountOne, "0x2", "transfer", 1, 100) // Returns 100 events
674+
func (client *Client) EventsByHandle(account AccountAddress, eventHandle string, fieldName string, start *uint64, limit *uint64) ([]*api.Event, error) {
675+
return client.nodeClient.EventsByHandle(account, eventHandle, fieldName, start, limit)
676+
}
677+
652678
// SubmitTransaction Submits an already signed transaction to the blockchain
653679
//
654680
// sender := NewEd25519Account()

examples/script_transaction/main.go

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// fungible_asset is an example of how to create and transfer fungible assets
2+
package main
3+
4+
import (
5+
"fmt"
6+
7+
"github.com/aptos-labs/aptos-go-sdk"
8+
"github.com/aptos-labs/aptos-go-sdk/bcs"
9+
"github.com/aptos-labs/aptos-go-sdk/crypto"
10+
)
11+
12+
const testEd25519PrivateKey = "ed25519-priv-0xc5338cd251c22daa8c9c9cc94f498cc8a5c7e1d2e75287a5dda91096fe64efa5"
13+
14+
const TransferScriptPayload = "0x008601a11ceb0b0700000a06010002030206050806070e1708252010451900000001010000010003060c05030d6170746f735f6163636f756e74087472616e736665720000000000000000000000000000000000000000000000000000000000000001166170746f733a3a7363726970745f636f6d706f7365720100000100050a000b010b0211000200020920000000000000000000000000000000000000000000000000000000000000000209086400000000000000"
15+
const TransferAmount = uint64(1_000)
16+
const FundAmount = uint64(100_000_000)
17+
18+
func example(networkConfig aptos.NetworkConfig) {
19+
// Create a client for Aptos
20+
client, err := aptos.NewClient(networkConfig)
21+
if err != nil {
22+
panic("Failed to create client:" + err.Error())
23+
}
24+
25+
// Create a sender locally
26+
key := crypto.Ed25519PrivateKey{}
27+
err = key.FromHex(testEd25519PrivateKey)
28+
if err != nil {
29+
panic("Failed to decode Ed25519 private key:" + err.Error())
30+
}
31+
sender, err := aptos.NewAccountFromSigner(&key)
32+
if err != nil {
33+
panic("Failed to create sender:" + err.Error())
34+
}
35+
36+
// Fund the sender with the faucet to create it on-chain
37+
println("SENDER: ", sender.Address.String())
38+
err = client.Fund(sender.Address, FundAmount)
39+
if err != nil {
40+
panic("Failed to fund sender:" + err.Error())
41+
}
42+
receiver := &aptos.AccountAddress{}
43+
44+
// Now run a script version
45+
fmt.Printf("\n== Now running script version ==\n")
46+
runScript(client, sender, receiver)
47+
48+
if err != nil {
49+
panic("Failed to get store balance:" + err.Error())
50+
}
51+
// fmt.Printf("After Script: Receiver Before transfer: %d, after transfer: %d\n", receiverAfterBalance, receiverAfterAfterBalance)
52+
}
53+
54+
func runScript(client *aptos.Client, alice *aptos.Account, bob *aptos.AccountAddress) {
55+
scriptBytes, err := aptos.ParseHex(TransferScriptPayload)
56+
if err != nil {
57+
panic("Failed to parse script:" + err.Error())
58+
}
59+
60+
var payload aptos.TransactionPayload
61+
payload.UnmarshalBCS(bcs.NewDeserializer(scriptBytes))
62+
63+
// 1. Build transaction
64+
rawTxn, err := client.BuildTransaction(alice.AccountAddress(), payload)
65+
66+
if err != nil {
67+
panic("Failed to build transaction:" + err.Error())
68+
}
69+
70+
// 2. Simulate transaction (optional)
71+
// This is useful for understanding how much the transaction will cost
72+
// and to ensure that the transaction is valid before sending it to the network
73+
// This is optional, but recommended
74+
simulationResult, err := client.SimulateTransaction(rawTxn, alice)
75+
if err != nil {
76+
panic("Failed to simulate transaction:" + err.Error())
77+
}
78+
fmt.Printf("\n=== Simulation ===\n")
79+
fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice)
80+
fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed)
81+
fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice)
82+
fmt.Printf("Status: %s\n", simulationResult[0].VmStatus)
83+
84+
// 3. Sign transaction
85+
signedTxn, err := rawTxn.SignedTransaction(alice)
86+
if err != nil {
87+
panic("Failed to sign transaction:" + err.Error())
88+
}
89+
90+
// 4. Submit transaction
91+
submitResult, err := client.SubmitTransaction(signedTxn)
92+
if err != nil {
93+
panic("Failed to submit transaction:" + err.Error())
94+
}
95+
txnHash := submitResult.Hash
96+
97+
// 5. Wait for the transaction to complete
98+
_, err = client.WaitForTransaction(txnHash)
99+
if err != nil {
100+
panic("Failed to wait for transaction:" + err.Error())
101+
}
102+
}
103+
104+
// main This example shows how to send a transaction with a script
105+
func main() {
106+
example(aptos.DevnetConfig)
107+
}

nodeClient.go

+88-3
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,91 @@ func (rc *NodeClient) AccountTransactions(account AccountAddress, start *uint64,
407407
})
408408
}
409409

410+
func (rc *NodeClient) EventsByHandle(
411+
account AccountAddress,
412+
eventHandle string,
413+
fieldName string,
414+
start *uint64,
415+
limit *uint64,
416+
) (data []*api.Event, err error) {
417+
basePath := fmt.Sprintf("accounts/%s/events/%s/%s",
418+
account.String(),
419+
eventHandle,
420+
fieldName)
421+
422+
baseUrl := rc.baseUrl.JoinPath(basePath)
423+
424+
const eventsPageSize = 100
425+
var effectiveLimit uint64
426+
if limit == nil {
427+
effectiveLimit = eventsPageSize
428+
} else {
429+
effectiveLimit = *limit
430+
}
431+
432+
var effectiveStart uint64
433+
if start == nil {
434+
effectiveStart = 0
435+
} else {
436+
effectiveStart = *start
437+
}
438+
439+
if effectiveLimit <= eventsPageSize {
440+
params := url.Values{}
441+
params.Set("start", strconv.FormatUint(effectiveStart, 10))
442+
params.Set("limit", strconv.FormatUint(effectiveLimit, 10))
443+
444+
requestUrl := *baseUrl
445+
requestUrl.RawQuery = params.Encode()
446+
447+
data, err = Get[[]*api.Event](rc, requestUrl.String())
448+
if err != nil {
449+
return nil, fmt.Errorf("get events api err: %w", err)
450+
}
451+
return data, nil
452+
}
453+
454+
pages := (effectiveLimit + eventsPageSize - 1) / eventsPageSize
455+
channels := make([]chan ConcResponse[[]*api.Event], pages)
456+
457+
for i := uint64(0); i < pages; i++ {
458+
channels[i] = make(chan ConcResponse[[]*api.Event], 1)
459+
pageStart := effectiveStart + (i * eventsPageSize)
460+
pageLimit := min(eventsPageSize, effectiveLimit-(i*eventsPageSize))
461+
462+
go fetch(func() ([]*api.Event, error) {
463+
params := url.Values{}
464+
params.Set("start", strconv.FormatUint(pageStart, 10))
465+
params.Set("limit", strconv.FormatUint(pageLimit, 10))
466+
467+
requestUrl := *baseUrl
468+
requestUrl.RawQuery = params.Encode()
469+
470+
events, err := Get[[]*api.Event](rc, requestUrl.String())
471+
if err != nil {
472+
return nil, fmt.Errorf("get events api err: %w", err)
473+
}
474+
return events, nil
475+
}, channels[i])
476+
}
477+
478+
events := make([]*api.Event, 0, effectiveLimit)
479+
for i, ch := range channels {
480+
response := <-ch
481+
if response.Err != nil {
482+
return nil, response.Err
483+
}
484+
events = append(events, response.Result...)
485+
close(channels[i])
486+
}
487+
488+
sort.Slice(events, func(i, j int) bool {
489+
return events[i].SequenceNumber < events[j].SequenceNumber
490+
})
491+
492+
return events, nil
493+
}
494+
410495
// handleTransactions is a helper function for fetching transactions
411496
//
412497
// It will fetch the transactions from the node in a single request if possible, otherwise it will fetch them concurrently.
@@ -1051,11 +1136,11 @@ func Get[T any](rc *NodeClient, getUrl string) (out T, err error) {
10511136
err = NewHttpError(response)
10521137
return out, err
10531138
}
1139+
defer response.Body.Close()
10541140
blob, err := io.ReadAll(response.Body)
10551141
if err != nil {
10561142
return out, fmt.Errorf("error getting response data, %w", err)
10571143
}
1058-
_ = response.Body.Close()
10591144
err = json.Unmarshal(blob, &out)
10601145
if err != nil {
10611146
return out, err
@@ -1086,12 +1171,12 @@ func (rc *NodeClient) GetBCS(getUrl string) (out []byte, err error) {
10861171
err = NewHttpError(response)
10871172
return
10881173
}
1174+
defer response.Body.Close()
10891175
blob, err := io.ReadAll(response.Body)
10901176
if err != nil {
10911177
err = fmt.Errorf("error getting response data, %w", err)
10921178
return
10931179
}
1094-
_ = response.Body.Close()
10951180
return blob, nil
10961181
}
10971182

@@ -1121,12 +1206,12 @@ func Post[T any](rc *NodeClient, postUrl string, contentType string, body io.Rea
11211206
err = NewHttpError(response)
11221207
return data, err
11231208
}
1209+
defer response.Body.Close()
11241210
blob, err := io.ReadAll(response.Body)
11251211
if err != nil {
11261212
err = fmt.Errorf("error getting response data, %w", err)
11271213
return data, err
11281214
}
1129-
_ = response.Body.Close()
11301215

11311216
err = json.Unmarshal(blob, &data)
11321217
return data, err

0 commit comments

Comments
 (0)