Skip to content

Commit

Permalink
Merge branch 'BCFR-1071-Generic-MultiNodeClient' of https://github.co…
Browse files Browse the repository at this point in the history
…m/smartcontractkit/chainlink-solana into BCFR-1071-Generic-MultiNodeClient
  • Loading branch information
DylanTinianov committed Dec 11, 2024
2 parents 8fba411 + 1aec457 commit df4bd68
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/e2e_custom_cl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ on:
required: true
default: develop
type: string
team:
description: Team to run the tests for (e.g. BIX, CCIP)
required: true
type: string

env:
CL_ECR: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink
Expand Down Expand Up @@ -335,6 +339,7 @@ jobs:
env:
E2E_TEST_CHAINLINK_IMAGE: ${{ env.CL_ECR }}
E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret
CHAINLINK_USER_TEAM: ${{ github.event.inputs.team || 'BIX' }}

e2e_program_upgrade_tests:
name: E2E Program Upgrade Tests
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/e2e_testnet_daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ on:
description: 'Key to run tests with custom test secrets like ws url, rpc url, private key, etc.'
required: false
type: string
team:
description: Team to run the tests for (e.g. BIX, CCIP)
required: true
type: string
schedule:
- cron: '0 6 * * *'
# Only run 1 of this workflow at a time per PR
Expand Down Expand Up @@ -175,4 +179,5 @@ jobs:
E2E_TEST_COMMON_PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }} # default private key
E2E_TEST_COMMON_RPC_URL: https://api.devnet.solana.com # default url
E2E_TEST_COMMON_WS_URL: https://api.devnet.solana.com # default url
E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret
E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret
CHAINLINK_USER_TEAM: ${{ github.event.inputs.team || 'BIX' }}
9 changes: 8 additions & 1 deletion .github/workflows/soak.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ on:
default: develop
required: true
type: string
team:
description: Team to run the tests for (e.g. BIX, CCIP)
required: true
type: string
default: BIX
env:
CL_ECR: ${{ secrets.QA_AWS_ACCOUNT_NUMBER }}.dkr.ecr.${{ secrets.QA_AWS_REGION }}.amazonaws.com/chainlink
CONTRACT_ARTIFACTS_PATH: contracts/target/deploy
Expand All @@ -41,6 +46,7 @@ jobs:
e2e_custom_build_artifacts:
name: E2E Custom Build Artifacts
environment: integration
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
Expand Down Expand Up @@ -116,4 +122,5 @@ jobs:
cache_key_id: solana-e2e-${{ env.MOD_CACHE_VERSION }}
cache_restore_only: "false"
env:
E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret
E2E_TEST_SOLANA_SECRET: thisisatestingonlysecret
CHAINLINK_USER_TEAM: ${{ inputs.team }}
14 changes: 14 additions & 0 deletions integration-tests/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,10 +384,24 @@ func BuildNodeContractPairID(node *client.ChainlinkClient, ocr2Addr string) (str
}

func (c *Common) Default(t *testing.T, namespacePrefix string) (*Common, error) {
productName := "data-feedsv2.0"
nsLabels, err := environment.GetRequiredChainLinkNamespaceLabels(productName, "soak")
if err != nil {
return nil, err
}

workloadPodLabels, err := environment.GetRequiredChainLinkWorkloadAndPodLabels(productName, "soak")
if err != nil {
return nil, err
}

c.TestEnvDetails.K8Config = &environment.Config{
NamespacePrefix: fmt.Sprintf("solana-%s", namespacePrefix),
TTL: c.TestEnvDetails.TestDuration,
Test: t,
Labels: nsLabels,
WorkloadLabels: workloadPodLabels,
PodLabels: workloadPodLabels,
}

if *c.TestConfig.Common.InsideK8s {
Expand Down
8 changes: 3 additions & 5 deletions pkg/solana/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,14 +278,12 @@ func TestClient_GetBlocks(t *testing.T) {

// Verify we can retrieve blocks
startSlot := uint64(1)
endSlot := uint64(6)
endSlot := uint64(10)
require.Eventually(t,
func() bool {
blocks, err := c.GetBlocks(ctx, startSlot, &endSlot)
if err != nil {
return false
}
return len(blocks) == 5
require.NoError(t, err) // don't mask error within false
return len(blocks) >= 2 // slots != blocks (expect multiple blocks for 10 slots)
},
requestTimeout, 500*time.Millisecond)
}
Expand Down
30 changes: 26 additions & 4 deletions pkg/solana/codec/solana.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,28 @@ func NewNamedModifierCodec(original types.RemoteCodec, itemType string, modifier
return modCodec, err
}

func NewIDLInstructionsCodec(idl IDL, builder encodings.Builder) (types.RemoteCodec, error) {
typeCodecs := make(encodings.LenientCodecFromTypeCodec)
caser := cases.Title(language.English)
refs := &codecRefs{
builder: builder,
codecs: make(map[string]encodings.TypeCodec),
typeDefs: idl.Types,
dependencies: make(map[string][]string),
}

for _, instruction := range idl.Instructions {
name, instCodec, err := asStruct(instruction.Args, refs, instruction.Name, caser, false)
if err != nil {
return nil, err
}

typeCodecs[name] = instCodec
}

return typeCodecs, nil
}

// NewIDLAccountCodec is for Anchor custom types
func NewIDLAccountCodec(idl IDL, builder encodings.Builder) (types.RemoteCodec, error) {
return newIDLCoded(idl, builder, idl.Accounts, true)
Expand Down Expand Up @@ -115,7 +137,7 @@ func createNamedCodec(

switch def.Type.Kind {
case IdlTypeDefTyKindStruct:
return asStruct(def, refs, name, caser, includeDiscriminator)
return asStruct(*def.Type.Fields, refs, name, caser, includeDiscriminator)
case IdlTypeDefTyKindEnum:
variants := def.Type.Variants
if !variants.IsAllUint8() {
Expand All @@ -129,7 +151,7 @@ func createNamedCodec(
}

func asStruct(
def IdlTypeDef,
fields []IdlField,
refs *codecRefs,
name string, // name is the struct name and can be used in dependency checks
caser cases.Caser,
Expand All @@ -139,13 +161,13 @@ func asStruct(
if includeDiscriminator {
desLen = 1
}
named := make([]encodings.NamedTypeCodec, len(*def.Type.Fields)+desLen)
named := make([]encodings.NamedTypeCodec, len(fields)+desLen)

if includeDiscriminator {
named[0] = encodings.NamedTypeCodec{Name: "Discriminator" + name, Codec: NewDiscriminator(name)}
}

for idx, field := range *def.Type.Fields {
for idx, field := range fields {
fieldName := field.Name

typedCodec, err := processFieldType(name, field.Type, refs)
Expand Down
11 changes: 11 additions & 0 deletions pkg/solana/fees/computebudget.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ func set(tx *solana.Transaction, baseData instruction, appendToFront bool) error

// https://github.com/gagliardetto/solana-go/blob/618f56666078f8131a384ab27afd918d248c08b7/transaction.go#L293
tx.Message.Header.NumReadonlyUnsignedAccounts++

// lookup table addresses are indexed after the tx.Message.AccountKeys
// higher indices must be increased
// https://github.com/gagliardetto/solana-go/blob/da2193071f56059aa35010a239cece016c4e827f/transaction.go#L440
for i, ix := range tx.Message.Instructions {
for j, v := range ix.Accounts {
if int(v) >= programIdx {
tx.Message.Instructions[i].Accounts[j]++
}
}
}
}

// get instruction data
Expand Down
50 changes: 50 additions & 0 deletions pkg/solana/fees/computebudget_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/programs/system"
"github.com/gagliardetto/solana-go/programs/token"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -136,6 +137,55 @@ func testSet[V instruction](t *testing.T, builder func(uint) V, setter func(*sol
assert.NoError(t, err)
assert.Equal(t, data, []byte(tx.Message.Instructions[computeIndex].Data))
})

t.Run("with_lookuptables", func(t *testing.T) {
t.Parallel()

// build base tx (no fee)
tx, err := solana.NewTransaction([]solana.Instruction{
system.NewTransferInstruction(
1,
solana.PublicKey{1},
solana.PublicKey{2},
).Build(),
token.NewTransferInstruction(
uint64(1),
solana.PublicKey{11},
solana.PublicKey{12},
solana.PublicKey{13},
[]solana.PublicKey{
solana.PublicKey{14},
},
).Build(),
},
solana.Hash{},
solana.TransactionAddressTables(map[solana.PublicKey]solana.PublicKeySlice{
solana.PublicKey{}: solana.PublicKeySlice{solana.PublicKey{1}, solana.PublicKey{2}, solana.PublicKey{11}, solana.PublicKey{12}, solana.PublicKey{13}, solana.PublicKey{14}},
}),
)
require.NoError(t, err)

// check current account indices
assert.Equal(t, 2, len(tx.Message.Instructions))
assert.Equal(t, []uint16{0, 4}, tx.Message.Instructions[0].Accounts)
assert.Equal(t, []uint16{5, 6, 7, 1}, tx.Message.Instructions[1].Accounts)
assert.Equal(t, 4, len(tx.Message.AccountKeys))

// add fee
require.NoError(t, setter(tx, builder(0)))

// evaluate
assert.Equal(t, 3, len(tx.Message.Instructions))
computeUnitIndex := getIndex(len(tx.Message.Instructions))
transferIndex := 0
if computeUnitIndex == transferIndex {
transferIndex = 1
}
assert.Equal(t, 5, len(tx.Message.AccountKeys))
assert.Equal(t, uint16(4), tx.Message.Instructions[computeUnitIndex].ProgramIDIndex)
assert.Equal(t, []uint16{0, 5}, tx.Message.Instructions[transferIndex].Accounts)
assert.Equal(t, []uint16{6, 7, 8, 1}, tx.Message.Instructions[transferIndex+1].Accounts)
})
}

func TestParse(t *testing.T) {
Expand Down
44 changes: 36 additions & 8 deletions pkg/solana/txm/txm.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func (txm *Txm) confirm() {
if res[i].Err != nil {
// Process error to determine the corresponding state and type.
// Skip marking as errored if error considered to not be a failure.
if txState, errType := txm.processError(s[i], res[i].Err, false); errType != NoFailure {
if txState, errType := txm.ProcessError(s[i], res[i].Err, false); errType != NoFailure {
id, err := txm.txs.OnError(s[i], txm.cfg.TxRetentionTimeout(), txState, errType)
if err != nil {
txm.lggr.Infow(fmt.Sprintf("failed to mark transaction as %s", txState.String()), "id", id, "signature", s[i], "error", err)
Expand Down Expand Up @@ -545,7 +545,7 @@ func (txm *Txm) simulate() {
}
// Process error to determine the corresponding state and type.
// Certain errors can be considered not to be failures during simulation to allow the process to continue
if txState, errType := txm.processError(msg.signatures[0], res.Err, true); errType != NoFailure {
if txState, errType := txm.ProcessError(msg.signatures[0], res.Err, true); errType != NoFailure {
id, err := txm.txs.OnError(msg.signatures[0], txm.cfg.TxRetentionTimeout(), txState, errType)
if err != nil {
txm.lggr.Errorw(fmt.Sprintf("failed to mark transaction as %s", txState.String()), "id", id, "err", err)
Expand Down Expand Up @@ -702,7 +702,7 @@ func (txm *Txm) EstimateComputeUnitLimit(ctx context.Context, tx *solanaGo.Trans
}
// Process error to determine the corresponding state and type.
// Certain errors can be considered not to be failures during simulation to allow the process to continue
if txState, errType := txm.processError(sig, res.Err, true); errType != NoFailure {
if txState, errType := txm.ProcessError(sig, res.Err, true); errType != NoFailure {
err := txm.txs.OnPrebroadcastError(id, txm.cfg.TxRetentionTimeout(), txState, errType)
if err != nil {
return 0, fmt.Errorf("failed to process error %v for tx ID %s: %w", res.Err, id, err)
Expand Down Expand Up @@ -746,7 +746,7 @@ func (txm *Txm) simulateTx(ctx context.Context, tx *solanaGo.Transaction) (res *
}

// processError parses and handles relevant errors found in simulation results
func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState TxState, errType TxErrType) {
func (txm *Txm) ProcessError(sig solanaGo.Signature, resErr interface{}, simulation bool) (txState TxState, errType TxErrType) {
if resErr != nil {
// handle various errors
// https://github.com/solana-labs/solana/blob/master/sdk/src/transaction/error.rs
Expand All @@ -773,10 +773,6 @@ func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulat
return txState, NoFailure
}
return Errored, errType
// transaction will encounter execution error/revert
case strings.Contains(errStr, "InstructionError"):
txm.lggr.Debugw("InstructionError", logValues...)
return Errored, errType
// transaction is already processed in the chain
case strings.Contains(errStr, "AlreadyProcessed"):
txm.lggr.Debugw("AlreadyProcessed", logValues...)
Expand All @@ -786,6 +782,38 @@ func (txm *Txm) processError(sig solanaGo.Signature, resErr interface{}, simulat
return txState, NoFailure
}
return Errored, errType
// transaction will encounter execution error/revert
case strings.Contains(errStr, "InstructionError"):
txm.lggr.Debugw("InstructionError", logValues...)
return FatallyErrored, errType
// transaction contains an invalid account reference
case strings.Contains(errStr, "InvalidAccountIndex"):
txm.lggr.Debugw("InvalidAccountIndex", logValues...)
return FatallyErrored, errType
// transaction loads a writable account that cannot be written
case strings.Contains(errStr, "InvalidWritableAccount"):
txm.lggr.Debugw("InvalidWritableAccount", logValues...)
return FatallyErrored, errType
// address lookup table not found
case strings.Contains(errStr, "AddressLookupTableNotFound"):
txm.lggr.Debugw("AddressLookupTableNotFound", logValues...)
return FatallyErrored, errType
// attempted to lookup addresses from an invalid account
case strings.Contains(errStr, "InvalidAddressLookupTableData"):
txm.lggr.Debugw("InvalidAddressLookupTableData", logValues...)
return FatallyErrored, errType
// address table lookup uses an invalid index
case strings.Contains(errStr, "InvalidAddressLookupTableIndex"):
txm.lggr.Debugw("InvalidAddressLookupTableIndex", logValues...)
return FatallyErrored, errType
// attempt to debit an account but found no record of a prior credit.
case strings.Contains(errStr, "AccountNotFound"):
txm.lggr.Debugw("AccountNotFound", logValues...)
return FatallyErrored, errType
// attempt to load a program that does not exist
case strings.Contains(errStr, "ProgramAccountNotFound"):
txm.lggr.Debugw("ProgramAccountNotFound", logValues...)
return FatallyErrored, errType
// unrecognized errors (indicates more concerning failures)
default:
// if simulating, return TxFailSimOther if error unknown
Expand Down
4 changes: 2 additions & 2 deletions pkg/solana/txm/txm_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,7 @@ func TestTxm_disabled_confirm_timeout_with_retention(t *testing.T) {
// check transaction status which should still be stored
status, err := txm.GetTransactionStatus(ctx, testTxID)
require.NoError(t, err)
require.Equal(t, types.Failed, status)
require.Equal(t, types.Fatal, status)

// Sleep until retention period has passed for transaction and for another reap cycle to run
time.Sleep(15 * time.Second)
Expand Down Expand Up @@ -1089,7 +1089,7 @@ func TestTxm_compute_unit_limit_estimation(t *testing.T) {
// tx should be stored in-memory and moved to errored state
status, err := txm.GetTransactionStatus(ctx, txID)
require.NoError(t, err)
require.Equal(t, commontypes.Failed, status)
require.Equal(t, commontypes.Fatal, status)
})
}

Expand Down
Loading

0 comments on commit df4bd68

Please sign in to comment.