Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e7f8bb4

Browse files
committedMar 18, 2025·
[simulation] Add simulation for fee payer / multikey
1 parent 214ab6c commit e7f8bb4

File tree

8 files changed

+160
-21
lines changed

8 files changed

+160
-21
lines changed
 

‎client.go

+8
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ type AptosRpcClient interface {
301301
// simResponse, err := client.SimulateTransaction(rawTxn, sender)
302302
SimulateTransaction(rawTxn *RawTransaction, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error)
303303

304+
// SimulateTransactionMultiAgent simulates a transaction as fee payer or multi agent
305+
SimulateTransactionMultiAgent(rawTxn *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error)
306+
304307
// GetChainId Retrieves the ChainId of the network
305308
// Note this will be cached forever, or taken directly from the config
306309
GetChainId() (chainId uint8, err error)
@@ -749,6 +752,11 @@ func (client *Client) SimulateTransaction(rawTxn *RawTransaction, sender Transac
749752
return client.nodeClient.SimulateTransaction(rawTxn, sender, options...)
750753
}
751754

755+
// SimulateTransactionMultiAgent simulates a transaction as fee payer or multi agent
756+
func (client *Client) SimulateTransactionMultiAgent(rawTxn *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) {
757+
return client.nodeClient.SimulateTransactionMultiAgent(rawTxn, sender, options...)
758+
}
759+
752760
// GetChainId Retrieves the ChainId of the network
753761
// Note this will be cached forever, or taken directly from the config
754762
func (client *Client) GetChainId() (chainId uint8, err error) {

‎client_test.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,7 @@ func testTransactionSimulation(t *testing.T, createAccount CreateSigner, buildTr
173173
simulatedTxn, err := client.SimulateTransaction(rawTxn, account)
174174
switch account.(type) {
175175
case *MultiKeyTestSigner:
176-
// multikey simulation currently not supported
177-
assert.Error(t, err)
178-
assert.ErrorContains(t, err, "currently unsupported sender derivation scheme")
176+
// multikey simulation currently not supported, skip it for now
179177
return // skip rest of the tests
180178
default:
181179
assert.NoError(t, err)

‎crypto/authenticator.go

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const (
3838
AccountAuthenticatorMultiEd25519 AccountAuthenticatorType = 1 // AccountAuthenticatorMultiEd25519 is the authenticator type for multi-ed25519 accounts
3939
AccountAuthenticatorSingleSender AccountAuthenticatorType = 2 // AccountAuthenticatorSingleSender is the authenticator type for single-key accounts
4040
AccountAuthenticatorMultiKey AccountAuthenticatorType = 3 // AccountAuthenticatorMultiKey is the authenticator type for multi-key accounts
41+
AccountAuthenticatorNone AccountAuthenticatorType = 4 // AccountAuthenticatorNone is for simulation only, and allows for simulating any authenticator, it is rejected in normal submission
4142
)
4243

4344
// AccountAuthenticator a generic authenticator type for a transaction

‎crypto/simulation.go

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package crypto
2+
3+
import "github.com/aptos-labs/aptos-go-sdk/bcs"
4+
5+
//region NoAuthenticator
6+
7+
// NoAuthenticator represents a verifiable signature with it's accompanied public key
8+
//
9+
// Implements:
10+
//
11+
// - [AccountAuthenticatorImpl]
12+
// - [bcs.Marshaler]
13+
// - [bcs.Unmarshaler]
14+
// - [bcs.Struct]
15+
type NoAuthenticator struct {
16+
}
17+
18+
//region NoAuthenticator AccountAuthenticatorImpl implementation
19+
20+
// PublicKey returns the [PublicKey] of the authenticator
21+
//
22+
// Implements:
23+
// - [AccountAuthenticatorImpl]
24+
func (ea *NoAuthenticator) PublicKey() PublicKey {
25+
return nil
26+
}
27+
28+
// Signature returns the [Signature] of the authenticator
29+
//
30+
// Implements:
31+
// - [AccountAuthenticatorImpl]
32+
func (ea *NoAuthenticator) Signature() Signature {
33+
return nil
34+
}
35+
36+
// Verify returns true if the authenticator can be cryptographically verified
37+
//
38+
// Implements:
39+
// - [AccountAuthenticatorImpl]
40+
func (ea *NoAuthenticator) Verify([]byte) bool {
41+
return false
42+
}
43+
44+
//endregion
45+
46+
//region NoAuthenticator bcs.Struct implementation
47+
48+
// MarshalBCS serializes the [NoAuthenticator] to BCS bytes
49+
//
50+
// Implements:
51+
// - [bcs.Marshaler]
52+
func (ea *NoAuthenticator) MarshalBCS(*bcs.Serializer) {
53+
// TODO: Double check nothing is needed here
54+
}
55+
56+
// UnmarshalBCS deserializes the [NoAuthenticator] from BCS bytes
57+
//
58+
// Sets [bcs.Deserializer.Error] if it fails to read the required bytes.
59+
//
60+
// Implements:
61+
// - [bcs.Unmarshaler]
62+
func (ea *NoAuthenticator) UnmarshalBCS(*bcs.Deserializer) {
63+
// TODO: Double check nothing is needed here
64+
}
65+
66+
func NoAccountAuthenticator() *AccountAuthenticator {
67+
return &AccountAuthenticator{
68+
Variant: AccountAuthenticatorNone,
69+
Auth: &NoAuthenticator{}}
70+
}
71+
72+
//endregion
73+
//endregion

‎examples/multi_agent/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
transfer_coin
1+
multi_agent

‎examples/multi_agent/main.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ func example(networkConfig aptos.NetworkConfig) {
8585
// This is useful for understanding how much the transaction will cost
8686
// and to ensure that the transaction is valid before sending it to the network
8787
// This is optional, but recommended
88-
// TODO: Support simulate transaction with multi-agent / fee payer
89-
/*simulationResult, err := client.SimulateTransaction(rawTxn, alice)
88+
simulationResult, err := client.SimulateTransactionMultiAgent(rawTxn, alice, aptos.AdditionalSigners{bob.Address})
9089
if err != nil {
9190
panic("Failed to simulate transaction:" + err.Error())
9291
}
@@ -95,7 +94,7 @@ func example(networkConfig aptos.NetworkConfig) {
9594
fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed)
9695
fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice)
9796
fmt.Printf("Status: %s\n", simulationResult[0].VmStatus)
98-
*/
97+
9998
// 3. Sign transaction with both parties separately, this would be on different machines or places
10099
aliceAuth, err := rawTxn.Sign(alice)
101100
if err != nil {

‎examples/sponsored_transaction/main.go

+14
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ func example(networkConfig aptos.NetworkConfig) {
8383
panic("Failed to build transaction:" + err.Error())
8484
}
8585

86+
// Simulate transaction (optional)
87+
// This is useful for understanding how much the transaction will cost
88+
// and to ensure that the transaction is valid before sending it to the network
89+
// This is optional, but recommended
90+
simulationResult, err := client.SimulateTransactionMultiAgent(rawTxn, alice, aptos.FeePayer(&sponsor.Address))
91+
if err != nil {
92+
panic("Failed to simulate transaction:" + err.Error())
93+
}
94+
fmt.Printf("\n=== Simulation ===\n")
95+
fmt.Printf("Gas unit price: %d\n", simulationResult[0].GasUnitPrice)
96+
fmt.Printf("Gas used: %d\n", simulationResult[0].GasUsed)
97+
fmt.Printf("Total gas fee: %d\n", simulationResult[0].GasUsed*simulationResult[0].GasUnitPrice)
98+
fmt.Printf("Status: %s\n", simulationResult[0].VmStatus)
99+
86100
// Sign transaction
87101
aliceAuth, err := rawTxn.Sign(alice)
88102
if err != nil {

‎nodeClient.go

+60-14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"github.com/aptos-labs/aptos-go-sdk/crypto"
89
"io"
910
"log/slog"
1011
"net/http"
@@ -16,7 +17,6 @@ import (
1617

1718
"github.com/aptos-labs/aptos-go-sdk/api"
1819
"github.com/aptos-labs/aptos-go-sdk/bcs"
19-
"github.com/aptos-labs/aptos-go-sdk/crypto"
2020
)
2121

2222
const (
@@ -676,18 +676,8 @@ type EstimateMaxGasAmount bool
676676
type EstimatePrioritizedGasUnitPrice bool
677677

678678
// SimulateTransaction simulates a transaction
679-
//
680-
// TODO: This needs to support RawTransactionWithData
681-
// TODO: Support multikey simulation
682679
func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) {
683680
// build authenticator for simulation
684-
derivationScheme := sender.PubKey().Scheme()
685-
switch derivationScheme {
686-
case crypto.MultiEd25519Scheme:
687-
case crypto.MultiKeyScheme:
688-
// todo: add support for multikey simulation on the node
689-
return nil, fmt.Errorf("currently unsupported sender derivation scheme %v", derivationScheme)
690-
}
691681
auth := sender.SimulationAuthenticator()
692682

693683
// generate signed transaction for simulation (with zero signature)
@@ -696,6 +686,63 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac
696686
return nil, err
697687
}
698688

689+
return rc.simulateTransactionInner(signedTxn, options...)
690+
}
691+
692+
// SimulateTransactionMultiAgent simulates a transaction as fee payer or multi agent
693+
func (rc *NodeClient) SimulateTransactionMultiAgent(rawTxn *RawTransactionWithData, sender TransactionSigner, options ...any) (data []*api.UserTransaction, err error) {
694+
expirationSeconds := DefaultExpirationSeconds
695+
696+
var feePayer *AccountAddress
697+
var additionalSigners []AccountAddress
698+
699+
for opti, option := range options {
700+
switch ovalue := option.(type) {
701+
case ExpirationSeconds:
702+
expirationSeconds = int64(ovalue)
703+
if expirationSeconds < 0 {
704+
err = errors.New("ExpirationSeconds cannot be less than 0")
705+
return nil, err
706+
}
707+
case FeePayer:
708+
feePayer = ovalue
709+
case AdditionalSigners:
710+
additionalSigners = ovalue
711+
default:
712+
err = fmt.Errorf("APTTransferTransaction arg [%d] unknown option type %T", opti+4, option)
713+
return nil, err
714+
}
715+
}
716+
717+
var signedTxn *SignedTransaction
718+
var ok bool
719+
if feePayer != nil {
720+
senderAuth := sender.SimulationAuthenticator()
721+
feePayerAuth := crypto.NoAccountAuthenticator()
722+
additionalSignersAuth := make([]crypto.AccountAuthenticator, len(additionalSigners))
723+
for i := range additionalSigners {
724+
additionalSignersAuth[i] = *crypto.NoAccountAuthenticator()
725+
}
726+
signedTxn, ok = rawTxn.ToFeePayerSignedTransaction(senderAuth, feePayerAuth, additionalSignersAuth)
727+
if !ok {
728+
return nil, fmt.Errorf("failed to convert fee payer signer to signed transaction")
729+
}
730+
} else {
731+
senderAuth := sender.SimulationAuthenticator()
732+
additionalSignersAuth := make([]crypto.AccountAuthenticator, len(additionalSigners))
733+
for i := range additionalSigners {
734+
additionalSignersAuth[i] = *crypto.NoAccountAuthenticator()
735+
}
736+
signedTxn, ok = rawTxn.ToMultiAgentSignedTransaction(senderAuth, additionalSignersAuth)
737+
if !ok {
738+
return nil, fmt.Errorf("failed to convert multi agent signer to signed transaction")
739+
}
740+
}
741+
742+
return rc.simulateTransactionInner(signedTxn, options...)
743+
}
744+
745+
func (rc *NodeClient) simulateTransactionInner(signedTxn *SignedTransaction, options ...any) (data []*api.UserTransaction, err error) {
699746
sblob, err := bcs.Serialize(signedTxn)
700747
if err != nil {
701748
return
@@ -705,7 +752,7 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac
705752

706753
// parse simulate tx options
707754
params := url.Values{}
708-
for i, arg := range options {
755+
for _, arg := range options {
709756
switch value := arg.(type) {
710757
case EstimateGasUnitPrice:
711758
params.Set("estimate_gas_unit_price", strconv.FormatBool(bool(value)))
@@ -714,8 +761,7 @@ func (rc *NodeClient) SimulateTransaction(rawTxn *RawTransaction, sender Transac
714761
case EstimatePrioritizedGasUnitPrice:
715762
params.Set("estimate_prioritized_gas_unit_price", strconv.FormatBool(bool(value)))
716763
default:
717-
err = fmt.Errorf("SimulateTransaction arg %d bad type %T", i+1, arg)
718-
return
764+
// Silently ignore unknown arguments, as there are multiple intakes
719765
}
720766
}
721767
if len(params) != 0 {

0 commit comments

Comments
 (0)
Please sign in to comment.