Skip to content
Merged
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
15 changes: 15 additions & 0 deletions solana/computer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,23 @@ func testUserRequestSystemCall(ctx context.Context, require *require.Assertions,
id = uuid.Must(uuid.NewV4()).String()
extra = []byte{ConfirmFlagNonceAvailable}
extra = append(extra, uuid.Must(uuid.FromString(c.RequestId)).Bytes()...)
mainNonce, err := node.store.ReadNonceAccount(ctx, c.NonceAccount)
require.Nil(err)
nonceHash := solana.MustHashFromBase58(mainNonce.Hash)
extra = append(extra, nonceHash[:]...)
extra = attachSystemCall(extra, cid, raw)

for _, invalid := range [][]byte{extra[:1+uuid.Size], extra[:1+uuid.Size+31]} {
out := testBuildObserverRequest(node, uuid.Must(uuid.NewV4()).String(), OperationTypeConfirmNonce, invalid)
for _, node := range nodes {
testStep(ctx, require, node, out)
call, err := node.store.ReadSystemCallByRequestId(ctx, c.RequestId, common.RequestStateInitial)
require.Nil(err)
require.NotNil(call)
require.False(call.WithdrawalTraces.Valid)
}
}

out = testBuildObserverRequest(node, id, OperationTypeConfirmNonce, extra)
for _, node := range nodes {
testStep(ctx, require, node, out)
Expand Down
41 changes: 36 additions & 5 deletions solana/mvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"encoding/hex"
"errors"
"fmt"
"math/big"
"strings"
Expand Down Expand Up @@ -317,6 +318,9 @@ func (node *Node) processConfirmNonce(ctx context.Context, req *store.Request) (
switch call.State {
case common.RequestStateInitial:
case common.RequestStateFailed:
if !req.Restored {
return node.failRequest(ctx, req, "")
}
outputState = common.RequestStateDone
default:
return node.failRequest(ctx, req, "")
Expand All @@ -326,16 +330,47 @@ func (node *Node) processConfirmNonce(ctx context.Context, req *store.Request) (
if err != nil || user == nil {
panic(fmt.Errorf("store.ReadUser(%s) => %v %v", call.UserIdFromPublicPath(), user, err))
}
mix, err := bot.NewMixAddressFromString(user.MixAddress)
if err != nil {
panic(err)
}
os, _, err := node.GetSystemCallReferenceOutputs(ctx, call.UserIdFromPublicPath(), call.RequestHash, byte(outputState))
logger.Printf("node.GetSystemCallReferenceTxs(%s) => %v %v", req.MixinHash.String(), os, err)
if err != nil {
panic(err)
}
// Insufficient refund funds trigger compaction after the call is marked Failed.
// On replay, only rebuild the refund; do not revalidate or execute the main call.
if call.State == common.RequestStateFailed {
return node.refundAndFailRequest(ctx, req, mix.Members(), int(mix.Threshold), call, os)
}

switch flag {
case ConfirmFlagNonceAvailable:
// Available: call UUID, nonce hash, then the optional prepare call.
if len(extra) < uuid.Size+32 {
return node.failRequest(ctx, req, "")
}
nonceHash := solana.HashFromBytes(extra[uuid.Size : uuid.Size+32])
Comment on lines +351 to +354
mainTx, err := solana.TransactionFromBase64(call.Raw)
if err != nil {
panic(err)
}
err = node.processTransactionWithAddressLookups(ctx, mainTx)
if err != nil {
if errors.Is(err, errInvalidAddressLookup) {
return node.refundAndFailRequest(ctx, req, mix.Members(), int(mix.Threshold), call, os)
}
panic(err)
Comment thread
hundredark marked this conversation as resolved.
Resolved
}
err = verifySystemCallNonce(mainTx, call.NonceAccount, nonceHash, node.SolanaPayer())
logger.Printf("verifySystemCallNonce(%s) => %v", call.RequestId, err)
if err != nil {
return node.refundAndFailRequest(ctx, req, mix.Members(), int(mix.Threshold), call, os)
}
as := node.GetSystemCallRelatedAsset(ctx, os)
var sessions []*store.Session
prepare, tx, err := node.getSubSystemCallFromExtra(ctx, req, extra[16:])
prepare, tx, err := node.getSubSystemCallFromExtra(ctx, req, extra[uuid.Size+32:])
if err != nil {
return node.failRequest(ctx, req, "")
}
Expand Down Expand Up @@ -423,10 +458,6 @@ func (node *Node) processConfirmNonce(ctx context.Context, req *store.Request) (
}
return txs, ""
case ConfirmFlagNonceExpired:
mix, err := bot.NewMixAddressFromString(user.MixAddress)
if err != nil {
panic(err)
}
return node.refundAndFailRequest(ctx, req, mix.Members(), int(mix.Threshold), call, os)
default:
logger.Printf("invalid nonce confirm flag: %d", flag)
Expand Down
2 changes: 2 additions & 0 deletions solana/observer.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ func (node *Node) handleUnconfirmedCalls(ctx context.Context) error {
}

if failureReason == "" {
nonceHash := solana.MustHashFromBase58(nonce.Hash)
extra = append(extra, nonceHash[:]...)
Comment on lines 646 to +648
err = node.OccupyNonceAccountByCall(ctx, nonce, call.RequestId)
if err != nil {
return err
Expand Down
39 changes: 39 additions & 0 deletions solana/system_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/MixinNetwork/safe/common"
"github.com/gagliardetto/solana-go"
computebudget "github.com/gagliardetto/solana-go/programs/compute-budget"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gofrs/uuid/v5"
"github.com/shopspring/decimal"
)
Expand Down Expand Up @@ -554,6 +555,44 @@ func (node *Node) buildSystemCallFromBytes(ctx context.Context, req *store.Reque
return call, tx, nil
}

func verifySystemCallNonce(tx *solana.Transaction, nonceAccount string, nonceHash solana.Hash, authority solana.PublicKey) error {
if tx.Message.RecentBlockhash != nonceHash {
return fmt.Errorf("invalid system call nonce hash")
}
if len(tx.Message.Instructions) == 0 {
return fmt.Errorf("system call has no nonce advance")
}
ix := tx.Message.Instructions[0]
program, err := tx.Message.Program(ix.ProgramIDIndex)
if err != nil {
return err
}
if program != system.ProgramID {
return fmt.Errorf("invalid nonce advance program: %s", program)
}
accounts, err := ix.ResolveInstructionAccounts(&tx.Message)
if err != nil {
return err
}
if len(accounts) != 3 {
return fmt.Errorf("invalid nonce advance accounts: %d", len(accounts))
}
advance, err := solanaApp.DecodeNonceAdvance(accounts, ix.Data)
if err != nil {
return err
}
if advance.GetNonceAccount().PublicKey.String() != nonceAccount || !advance.GetNonceAccount().IsWritable {
return fmt.Errorf("invalid nonce account")
}
if advance.GetNonceAuthorityAccount().PublicKey != authority || !advance.GetNonceAuthorityAccount().IsSigner {
return fmt.Errorf("invalid nonce authority")
}
if advance.GetSysVarRecentBlockHashesPubkeyAccount().PublicKey != solana.SysVarRecentBlockHashesPubkey {
return fmt.Errorf("invalid nonce recent blockhashes sysvar")
}
return nil
}

func (node *Node) checkUserSystemCall(ctx context.Context, tx *solana.Transaction) error {
if common.CheckTestEnvironment(ctx) {
return nil
Expand Down
Loading