Reputation: 1180
My Anchor transaction in Golang, despite working fine in Anchor JS code, doesn't want to play nice, and is giving me back an error message about the signer account (called authority
in this case) not having signed, even though I did in fact sign.
My instruction code structs look like this:
#[account]
#[derive(Default)]
pub struct Device {
pub ipv4: [u8; 4],
pub hostname: String,
bump: u8,
status: DeviceStatus,
local_key: Pubkey,
}
#[derive(Accounts)]
#[instruction(local_key: Pubkey)]
pub struct RegisterDevice<'info> {
#[account(init,
space = 128,
seeds = [local_key.as_ref()],
bump,
payer = authority,
)]
pub device: Box<Account<'info, Device>>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
My Anchor code that works fine looks like this:
it("registers a device", async () => {
const localKey = anchor.web3.Keypair.generate();
const [devicePDA, _devicePDABump] =
await anchor.web3.PublicKey.findProgramAddress(
[localKey.publicKey.toBuffer()],
program.programId
);
await program.methods
.registerDevice(localKey.publicKey)
.accounts({
device: devicePDA,
authority: provider.wallet.PublicKey,
systemProgram: SystemProgram.programId,
})
.rpc();
});
The Go code (using solana-go library):
registerDeviceInst, err := worknet.NewRegisterDeviceInstruction(
devicePubkey,
pdaPubkey,
*walletPubKey,
gagliardetto.SystemProgramID,
).ValidateAndBuild()
if err != nil {
return err
}
blockHash, err := client.GetRecentBlockhash(context.TODO(), gagliardettorpc.CommitmentFinalized)
if err != nil {
return nil
}
txn, err := gagliardetto.NewTransaction([]gagliardetto.Instruction{registerDeviceInst}, blockHash.Value.Blockhash)
if err != nil {
return err
}
_, err = txn.Sign(func(key gagliardetto.PublicKey) *gagliardetto.PrivateKey {
return walletPrivKey
})
if err != nil {
return err
}
Here's the dump of the TXN and response:
(*solana.Transaction)(0x14000110000)(
├─ Signatures[len=1]
│ └─ v1f9bZhMuJkbsrB54y2w36TQdpZDG4bxgbpwrkuD5xSbtCT9kshRnymKB28b2peb5u8YKDoAPRNz4rVz8Vt3pAY
├─ Message
│ ├─ RecentBlockhash: 6PXLreSEHvqP22AsGcAqFzt5Wv1oMGXH8WfUwVuDUtXH
│ ├─ AccountKeys[len=4]
│ │ ├─ HEwe5nVn4y2gV4tCQ3hZ6a3QxB9a72NyXstWxBk4q5ct
│ │ ├─ HqhAGJwjHo36AJwfPzaWAu224rVF9bDWnN9tx6JSdjot
│ │ ├─ 11111111111111111111111111111111
│ │ └─ EdUCoDdRnT5HsQ2Ejy3TWMTQP8iUyMQB4WzoNh45pNX9
│ └─ Header
│ ├─ NumRequiredSignatures: 1
│ ├─ NumReadonlySignedAccounts: 0
│ └─ NumReadonlyUnsignedAccounts: 2
└─ Instructions[len=1]
└─ Program: Worknet EdUCoDdRnT5HsQ2Ejy3TWMTQP8iUyMQB4WzoNh45pNX9
└─ Instruction: RegisterDevice
├─ Params[len=1]
│ └─ LocalKey: (solana.PublicKey) (len=32 cap=32) 8kb1Y6pkENNHnjzkeqWobdjnKTxiHD7fkjAEks2oEoyD
└─ Accounts[len=3]
├─ device: HqhAGJwjHo36AJwfPzaWAu224rVF9bDWnN9tx6JSdjot [WRITE]
├─ authority: HEwe5nVn4y2gV4tCQ3hZ6a3QxB9a72NyXstWxBk4q5ct [WRITE, SIGN]
└─ systemProgram: 11111111111111111111111111111111 []
)
daoctl: error: (*jsonrpc.RPCError)(0x1400038d1a0)({
Code: (int) -32002,
Message: (string) (len=90) "Transaction simulation failed: Error processing Instruction 0: custom program error: 0xbc2",
Data: (map[string]interface {}) (len=3) {
(string) (len=8) "accounts": (interface {}) <nil>,
(string) (len=3) "err": (map[string]interface {}) (len=1) {
(string) (len=16) "InstructionError": ([]interface {}) (len=2 cap=2) {
(json.Number) (len=1) "0",
(map[string]interface {}) (len=1) {
(string) (len=6) "Custom": (json.Number) (len=4) "3010"
}
}
},
(string) (len=4) "logs": ([]interface {}) (len=5 cap=8) {
(string) (len=63) "Program EdUCoDdRnT5HsQ2Ejy3TWMTQP8iUyMQB4WzoNh45pNX9 invoke [1]",
(string) (len=40) "Program log: Instruction: RegisterDevice",
(string) (len=151) "Program log: AnchorError caused by account: authority. Error Code: AccountNotSigner. Error Number: 3010. Error Message: The given account did not sign.",
(string) (len=90) "Program EdUCoDdRnT5HsQ2Ejy3TWMTQP8iUyMQB4WzoNh45pNX9 consumed 4085 of 200000 compute units",
(string) (len=88) "Program EdUCoDdRnT5HsQ2Ejy3TWMTQP8iUyMQB4WzoNh45pNX9 failed: custom program error: 0xbc2"
}
}
})
What magic is Anchor JS doing that I'm missing in my Go client code?
Upvotes: 1
Views: 2050
Reputation: 1180
OK, I've successfully figured out what went wrong here.
Reading over https://docs.solana.com/developing/programming-model/transactions, I noticed it mentions:
The Solana runtime ... verifies that each signature was signed by the private key corresponding to the public key at the same index in the message's account addresses array.
If you look at the accounts I was passing in the Go code, authority
was at index 1, not index 0 like its signature:
└─ Accounts[len=3]
├─ device: HqhAGJwjHo36AJwfPzaWAu224rVF9bDWnN9tx6JSdjot [WRITE]
├─ authority: HEwe5nVn4y2gV4tCQ3hZ6a3QxB9a72NyXstWxBk4q5ct [WRITE, SIGN]
└─ systemProgram: 11111111111111111111111111111111 []
So the solution was to change the order of the accounts in the Anchor struct definition, regenerate the Go bindings, and pass the accounts in the correct order so that the authority
account was index 0 in the transaction.
#[derive(Accounts)]
#[instruction(local_key: Pubkey)]
pub struct RegisterDevice<'info> {
+ #[account(mut)]
+ pub authority: Signer<'info>,
+
#[account(init,
space = 128,
seeds = [local_key.as_ref()],
bump,
payer = authority,
)]
pub device: Box<Account<'info, Device>>,
- #[account(mut)]
- pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
Now the transaction signs correctly. I think what was causing the Anchor code to work is that it will do some automatic account resolution in RPC calls, which correctly placed the authority/signatures (?) despite it being out-of-order in the generated Go code.
Upvotes: 4